#!/usr/bin/env python3
import json,re
from pathlib import Path

def exact(a,b,path='$'):
    if type(a) is not type(b):raise AssertionError('type '+path)
    if isinstance(a,dict):
        if set(a)!=set(b):raise AssertionError('keys '+path)
        for k in a:exact(a[k],b[k],path+'.'+k)
    elif isinstance(a,list):
        if len(a)!=len(b):raise AssertionError('length '+path)
        for i,(x,y) in enumerate(zip(a,b)):exact(x,y,f'{path}[{i}]')
    elif a!=b:raise AssertionError('value '+path)

def validate(lane):
    assert lane['error'] is None and len(lane['cases'])==26
    assert len(lane['findings'])==2 and not lane['actual_physical_deletes']
    positive=[];red=[]
    for c in lane['cases']:
        assert c['http_status']==200 and c['trace_format_valid'] is True
        if c['scenario'].endswith('_DELETE_OWN'):
            assert c['method']=='POST' and c['body']['status']==500
            assert c['body']['message']=='SHARED_ORIGINAL_ORDER_EXPRESS_ID_SCHEMA_ERROR_NOT_POSITIVE'
            e=c['original_error_evidence'];assert e['raw_response_private'] is True and e['mentions_missing_express_id'] is True
            assert re.fullmatch('[0-9a-f]{64}',e['original_message_sha256'])
            red.append(c)
        else:
            assert c['body']['status']==200
            if c['method']=='GET':assert c['complete_original_projection_equal'] is True
            positive.append(c)
    assert len(positive)==24 and len(red)==2
    for f in lane['findings']:assert f['classification']=='shared-original-order-express_id-missing-not-positive' and f['retained_row_unchanged'] is True
    exact(lane['retention'],{'mode':'retained-owned','fresh_exact_owned_rows':{'store':3,'terminal':3},'natural_sha_unchanged':True,'exact_pk_exclusions_only':True,'natural_rows':9},'retention')
    exact(lane['resources'],{'tokens_absent':True,'cache_files':0},'resources')
    exact(lane['independent_final_original'],{'new_pdo_owned_counts':{'store':3,'terminal':3},'all_pk_name_store_verified':True,'original_integer_epoch_windows_verified':True,'natural_express_sha_unchanged':True,'business_physical_delete_performed':False,'all_remaining_active':True,'private_error_wire_retained':True},'final original')
    assert len(lane['write_audit'])==8
    for e in lane['write_audit']:assert e['owned_scope_verified'] is True and e['table']=='yoshop_express' and e['operation'] in ('INSERT','UPDATE')
    assert not any(e.get('fingerprint_lexical_warning') for e in lane['events'])
    assert lane['loaded_boundary']['all_external_calls_forbidden'] is True and lane['loaded_boundary']['external_stubs'] is True
    assert lane['loaded_boundary']['store_model_sha']=='26d2e540317d083f36c3344c2118598c113d53c172fdb6d5684792a3f23598d9'
    assert lane['loaded_boundary']['terminal_model_sha']=='87b2fce74858c239618153160938bc7ae05eafb15fe7915b080c8a8755dbbcd0'
    return positive,red

def compare(b,c):
    bp,br=validate(b);cp,cr=validate(c)
    exact(bp,cp,'all positive full wire')
    for k in ('original_proofs','retention','resources','loaded_boundary','findings','independent_final_original'):exact(b[k],c[k],k)
    assert [x['scenario'] for x in br]==[x['scenario'] for x in cr]
    hashes=lambda lane:{k:v for e in lane['events'] for k,v in e.get('source_hashes',{}).items()}
    bh,ch=hashes(b),hashes(c)
    for k in set(bh)&set(ch):exact(bh[k],ch[k],'actual source '+k)
    project=lambda lane:[{k:e[k] for k in ('operation','table','tenant','owned_scope_verified')} for e in lane['write_audit']]
    exact(project(b),project(c),'all DML')
    actual=sorted({x['method']+' '+x['path'] for x in bp});assert len(actual)==8
    existing=['GET /store/setting.Express/list','GET /store/setting.Express/all']
    new=[x for x in actual if x not in existing];assert len(new)==6
    return {'actual_http_pairs':26,'strict_positive_http_pairs':24,'shared_original_delete_error_pairs_not_passed':2,'new_positive_methods':new,'existing_read_enhancements':existing,'strict_wire_type_value_array_order_differences':0,'actual_common_source_sha_count':len(set(bh)&set(ch)),'retained_active_rows_each_engine':6,'physical_business_deletes':0}

if __name__=='__main__':
    p=Path(__file__).parent;print(json.dumps(compare(json.loads((p/'evidence-B.json').read_text()),json.loads((p/'evidence-C.json').read_text())),indent=2))
