#!/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['error'] is None and len(lane['cases']) == 21 and len(lane['jobs']) == 8
    assert lane['published_count'] == 8 and lane['robot_count'] == 8
    for c in lane['cases']:
        assert c['http_status'] == 200 and c['trace_format_valid'] is True
        assert c['body']['status'] == (500 if c['scenario'] == 'PILOT_WT_REPEAT_DELETE_REFUSAL' else 200)
    for j in lane['jobs']:
        assert j['current_rows'] == 1 and j['prior_history_unchanged'] is True
        assert j['real_serve_service'] is True and j['http_count'] == 0
    assert [j['history_rows'] for j in lane['jobs']] == list(range(1, 9))
    assert lane['jobs'][-1]['full_independent_recommend_values']['height'] == '0.00'
    assert lane['jobs'][-1]['full_independent_recommend_values']['weight'] == '0.00'
    assert lane['retention']['mode'] == 'retained-owned'
    assert lane['retention']['fresh_pdo_counts'] == {'user':1,'staff':1,'staff_weight':6,'habit':0,'user_weight_status':0,'nurtition_target':0,'meal_recommend':1,'meal_recommend_history':8}
    assert lane['retention']['exact_owned_pk_exclusions_only'] is True
    assert lane['retention']['natural_small_table_sha_unchanged'] is True
    assert lane['retention']['large_output_full_sha'] is False
    for t, n in [('meal_recommend', 1), ('meal_recommend_history', 8)]:
        assert lane['retention']['large_output_after'][t]-lane['retention']['large_output_before'][t] == n
    assert lane['resource_cleanup'] == {'token_absent':True,'private_cache_files':0}
    assert len(lane['write_audit']) == 31
    for e in lane['write_audit']:
        assert e['owned_scope_verified'] is True
        assert e['table'] in ('yoshop_staff', 'yoshop_staff_weight', 'yoshop_meal_recommend', 'yoshop_meal_recommend_history')
    assert len(lane['boundary_findings']) == 1
    boundary = lane['boundary_findings'][0]
    assert boundary['business_status'] == 500 and boundary['missing_name_column_confirmed'] is True
    assert boundary['classification'] == 'shared-original-schema-contract-error-not-positive'


def compare(b, c):
    validate(b); validate(c)
    for key in ('cases', 'jobs', 'loaded_boundary'):
        exact(b[key], c[key], key)
    # C recovery revalidated the already committed duplicate measurement/staff
    # after private JSON 0.0->0 caused a physical-history false alarm. Assert the
    # additional evidence is an exact repeat of only that known pair, never
    # discard unmatched business values or arbitrary duplicate responses.
    bp, cp = b['original_proofs'], c['original_proofs']
    if len(cp) == len(bp)+2:
        exact(cp[3:5], cp[5:7], 'exact_recovery_repeat_measurement_staff')
        cp = cp[:5]+cp[7:]
    exact(bp, cp, 'all_original_proofs')
    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), 'write_audit')
    return {'http_pairs':21,'actual_positive_new_methods':['POST /store/weight.StaffWeight/add','POST /store/weight.StaffWeight/detail','POST /store/weight.StaffWeight/edit','POST /store/weight.StaffWeight/delete'],
            'existing_read_post_enhancements':['POST /store/weight.StaffWeight/list','POST /store/weight.StaffWeight/echartsList'],
            'shared_original_all_failure_not_positive':True,'real_job_pairs_not_http':8,'strict_wire_type_value_order_differences':0,
            'retained_owned_weight_rows_each':6,'physical_cleanup_performed':False}


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