"""Local, fictitious catalog projection. No network, API credentials or eBay writes.
The toy schema is NOT an eBay response, size chart or universal validator.
Run: python3 tailles_ebay.py
"""
from copy import deepcopy
from itertools import permutations
import json

CONTEXT = ('EBAY_FR', 'CHEMISES_DEMO', 'COLLECTION_A')
SNAPSHOT = {
    'context': CONTEXT, 'revision': 'R2', 'field': 'Taille',
    'allowed': {'S': ('ALPHA',), 'M': ('ALPHA',)},
    'required': True, 'variation': True,
}
ALIASES = {(CONTEXT, 'ALPHA', 'Small'): 'S'}  # merchant-validated, toy alias only


def project(row, snapshot, aliases, expected_revision):
    out = {'sku': row['sku'], 'quantity': row['quantity'], 'status': 'review', 'size': None}
    if row['context'] != snapshot['context']:
        return out | {'reason': 'wrong_scope'}
    if snapshot['revision'] != expected_revision:
        return out | {'reason': 'snapshot_changed'}
    raw = row['raw_size']
    if not raw:
        return out | {'reason': 'missing_size'}
    if not snapshot['variation']:
        return out | {'reason': 'variation_not_enabled'}
    value = aliases.get((row['context'], row['system'], raw), raw)
    if value not in snapshot['allowed']:
        return out | {'reason': 'unsupported_value'}
    if row['system'] not in snapshot['allowed'][value]:
        return out | {'reason': 'incompatible_system'}
    return out | {'status': 'candidate', 'reason': 'local_checks_passed',
                  'size': value, 'color': row['color'], 'group': row['group']}


def project_group(rows, snapshot=SNAPSHOT, aliases=ALIASES, expected_revision='R2'):
    if len({r['sku'] for r in rows}) != len(rows):
        raise ValueError('duplicate_input_sku')
    results = [project(r, snapshot, aliases, expected_revision) for r in rows]
    for result in results:
        if result['status'] != 'candidate':
            continue
        same = [x for x in results if x['status'] in ('candidate', 'collision')
                and (x.get('group'), x.get('color'), x.get('size')) ==
                (result['group'], result['color'], result['size'])]
        if len(same) > 1:
            for x in same:
                x.update(status='collision', reason='ambiguous_variant_tuple')
    return sorted(results, key=lambda r: r['sku'])


def row(sku='A', size='Small', color='Bleu', **changes):
    return dict(sku=sku, raw_size=size, color=color, system='ALPHA', quantity=4,
                group='G1', context=CONTEXT) | changes


def run():
    checks=[]
    def check(name, condition):
        assert condition, name
        checks.append(name)
    base=row(); original=deepcopy(base)
    out=project_group([base])[0]
    check('approved_alias_Small_to_S',out['size']=='S' and out['status']=='candidate')
    check('source_is_unchanged',base==original and base['raw_size']=='Small')
    check('sku_unchanged',out['sku']=='A')
    check('quantity_unchanged',out['quantity']==4)
    check('standard_value_unchanged',project_group([row(size='M')])[0]['size']=='M')
    for name,r,expected in [
        ('missing',row(size=''),'missing_size'),
        ('description_placeholder',row(size='Voir description'),'unsupported_value'),
        ('no_fuzzy_size',row(size='Smallish'),'unsupported_value'),
        ('no_numeric_conversion',row(size='38',system='EU'),'unsupported_value'),
        ('wrong_system',row(size='S',system='EU'),'incompatible_system'),
        ('wrong_market',row(context=('EBAY_DE','CHEMISES_DEMO','COLLECTION_A')),'wrong_scope'),
        ('wrong_category',row(context=('EBAY_FR','CHAUSSURES_DEMO','COLLECTION_A')),'wrong_scope'),
        ('wrong_collection',row(context=('EBAY_FR','CHEMISES_DEMO','COLLECTION_B')),'wrong_scope')]:
        check(name,project_group([r])[0]['reason']==expected)
    check('no_unapproved_alias',project_group([row()],aliases={})[0]['reason']=='unsupported_value')
    check('changed_snapshot_requires_review',project_group([row()],expected_revision='R1')[0]['reason']=='snapshot_changed')
    check('variation_disabled',project_group([row()],snapshot=SNAPSHOT|{'variation':False})[0]['reason']=='variation_not_enabled')
    collision=[row('A','Small'),row('B','S',quantity=6),row('C','M',color='Rouge',quantity=2)]
    results=project_group(collision)
    check('both_colliding_children_blocked',[x['status'] for x in results]==['collision','collision','candidate'])
    check('no_stock_sum_or_deletion',[(x['sku'],x['quantity']) for x in results]==[('A',4),('B',6),('C',2)])
    for i,p in enumerate(permutations(collision)):
        check('permutation_'+str(i),project_group(list(p))==results)
    separate=project_group([row('A','Small','Bleu'),row('B','S','Rouge')])
    check('same_size_different_colors_valid',all(x['status']=='candidate' for x in separate))
    groups=project_group([row('A','S'),row('B','S',group='G2')])
    check('separate_groups_not_merged',all(x['status']=='candidate' for x in groups))
    try:
        project_group([row(),row()])
    except ValueError as error:
        check('duplicate_input_rejected',str(error)=='duplicate_input_sku')
    else:
        raise AssertionError('duplicate_input_rejected')
    check('replay_deterministic',project_group(collision)==results)
    # Deliberately insufficient alternatives, each contradicted by a witness.
    check('refutes_every_nonempty_size_valid',bool('Voir description') and project_group([row(size='Voir description')])[0]['status']=='review')
    check('refutes_alias_without_context',ALIASES[(CONTEXT,'ALPHA','Small')]=='S' and project_group([row(context=('EBAY_DE','CHEMISES_DEMO','COLLECTION_A'))])[0]['status']=='review')
    check('refutes_dedup_then_sum',len(results)==3 and sum(x['status']=='candidate' for x in results)==1)
    check('cost_40_times_6_minutes',40*6==240 and 240/60==4)
    check('cost_10_extra',10*6==60)
    return {'checks':len(checks),'names':checks,'permutations':6,'badShortcuts':3,
            'results':results,'scope':'Toy merchant decisions only. No live taxonomy, physical equivalence, eBay update, publication or traffic verified.'}

if __name__=='__main__':
    print(json.dumps(run(),ensure_ascii=False,indent=2))
