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


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


def validate(lane):
    assert lane['lane'] in ('B','C')
    assert lane['error'] is None and not lane['findings']
    assert len(lane['cases']) == 12 and len(lane['jobs']) == 1
    assert lane['publish_count'] == lane['robot_count'] == 1
    for c in lane['cases']:
        assert c['method'] == 'POST' and c['http_status'] == c['body']['status'] == 200
        assert c['trace_format_valid'] is True
        if 'mysql_original_projection_equal' in c: assert c['mysql_original_projection_equal'] is True
    j=lane['jobs'][0]
    assert j['current_rows']==1 and j['history_rows']==5 and j['old_prefix_unchanged'] is True
    assert j['today_target_active'] is True and j['HTTP_count']==0
    assert j['full_independent_values']['recommend_power']=='2345.67'
    assert lane['retention']['natural_small_sha_unchanged'] is True
    assert lane['retention']['excludes_only_exact_owned_pks'] is True
    assert lane['retention']['large_full_sha'] is False
    assert lane['retention']['fresh_pdo_exact_pk_verified'] is True
    for t,n in [('meal_recommend',0),('meal_recommend_history',1)]:
        assert lane['retention']['large_after'][t]-lane['retention']['large_before'][t]==n
    counts={'user':1,'staff':1,'nurtrition':3 if lane['lane']=='B' else 1,'habit':0,'user_weight_status':0,'nurtition_target':4,'meal_recommend':1,'meal_recommend_history':5}
    exact(lane['retention']['counts'],counts,'exact retained counts')
    assert lane['resources']=={'token_absent':True,'cache_files':0}
    assert lane['independent_final_original_verification']=={
        'all_retained_exact_pks_verified':True,'owned_user_staff_chain_verified':True,
        'plan_list_all_columns_verified':True,'plan_list_pre_delete_active_and_post_delete_soft_flag_verified':True,
        'plan_list_fields_verified':11,'plan_id_mapping_only_after_original_ownership':True,'row_array_order_unchanged':True}
    assert len(lane['write_audit'])==5
    for e in lane['write_audit']: assert e['owned_scope_verified'] is True
    assert not any(e.get('fingerprint_lexical_warning') for e in lane['events'])
    assert lane['source_boundary']['nutrition_model_sha']=='a68cdfa33dfe1cac94823b546e43e0e4550185db6a1349fd699b2574989db9a9'
    assert lane['source_boundary']['target_service_sha']=='b94d0e4d1bddc46c7ac8be3d7cf6b2d9c164c9e2eb061d79b85e4ae4761aef7b'
    model_hashes={e['source_hashes']['app/store/model/nutrition/Nutrition.php'] for e in lane['events'] if 'app/store/model/nutrition/Nutrition.php' in e.get('source_hashes',{})}
    assert model_hashes=={'a68cdfa33dfe1cac94823b546e43e0e4550185db6a1349fd699b2574989db9a9'}


def compare(b,c):
    validate(b);validate(c)
    for key in ('cases','jobs','source_boundary','independent_final_original_verification'): exact(b[key],c[key],key)
    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 common source '+k)
    assert len(b.get('mysql_original_before_after_wire',[]))==5
    bp,cp=b['original_proofs'],c['original_proofs']
    assert len(bp)==len(cp)==10
    for i,(x,y) in enumerate(zip(bp,cp)):
        if x['kind']=='plan':
            assert x['source_owned_rows']==3 and y['source_owned_rows']==1
            x={k:v for k,v in x.items() if k!='source_owned_rows'}
            y={k:v for k,v in y.items() if k!='source_owned_rows'}
        exact(x,y,f'original_proofs[{i}]')
    project=lambda lane:[{k:e[k] for k in ('operation','table','owned_scope_verified')} for e in lane['write_audit']]
    exact(project(b),project(c),'all audited DML')
    methods=sorted({x['method']+' '+x['path'] for x in b['cases']})
    assert len(methods)==6
    return {'http_pairs':12,'real_job_pairs_not_http':1,'actual_positive_methods':methods,'strict_wire_type_value_array_order_differences':0,'common_actual_recorder_source_hashes':len(set(bh)&set(ch)),
            'red_before_patch_preserved':True,'retention':'owned soft-deleted plans B3/C1; User1 Staff1 Target4 Current1 History5 each',
            'all_retained_source_pks_independently_verified':True,'physical_cleanup_performed':False}


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