#!/usr/bin/env python3
"""Offline exact-integer/DTO/native-binary64 verifier; never replay INSERT."""
import copy,fractions,hashlib,json,pathlib,struct
P=pathlib.Path(__file__).resolve().parent
IDS=[18446744073709551615,9223372036854775807,9007199254740993,9007199254740991,0]
FULL=['log_id','user_id','scene','money','balance_before','balance_after','biz_type','biz_order_id','biz_order_no','describe','remark','operator_id','operator_name','create_time']
def strict_equal(a,b):
    if type(a) is not type(b):return False
    if isinstance(a,dict):return a.keys()==b.keys() and all(strict_equal(a[k],b[k]) for k in a)
    if isinstance(a,list):return len(a)==len(b) and all(strict_equal(x,y) for x,y in zip(a,b))
    return a==b
def validate(b,c,old,pb,pc,ab,ac):
    assert b['status']=='pass' and b['assertions']==100 and b['actual_prepares']==7
    assert c['exit_code']==c['stderr_bytes']==0
    r=c['report'];assert r['status']=='pass' and r['assertions']==168 and r['actual_prepares']==10 and r['business_select_executions']==14
    assert b['sample_rows']==r['sample_rows']==5 and b['wide_above_2pow53_samples']==r['wide_above_2pow53_samples']==3
    assert strict_equal(b['source_without_pk'],r['source_without_pk'])
    for lane,x,uid,pks in [('B',b,46912,[344,343,342,341,340]),('C',r,1926,[68,67,66,65,64])]:
        assert x['truth_before_sha256']==x['truth_after_sha256']
        assert x['file_cache_cold_hot_equal'] is True and x['hot_additional_prepares']==0
        assert x['global_p0_closed'] is False and x['dml']==x['ddl']==0
        assert x['timezone']=='Asia/Shanghai'
        assert len(x['full_model_dto'])==5
        wire=x['full_model_wire'];parsed=json.loads(wire)
        assert strict_equal(parsed,x['full_model_dto'])
        assert '"biz_order_id":9007199254740993,' in wire and '"biz_order_id":9223372036854775807,' in wire and '"biz_order_id":"18446744073709551615",' in wire
        for i,row in enumerate(parsed):
            assert list(row)==FULL and row['log_id']==pks[i] and row['user_id']==uid and row['scene']==30
            assert row['biz_order_id']==(str(IDS[i]) if i==0 else IDS[i])
            assert type(row['biz_order_id']) is (str if i==0 else int)
            assert row['money']==row['balance_before']==row['balance_after']=='0.00'
            assert row['create_time']=='2001-01-01 00:00:00'
            assert row['biz_order_no']=='CDXP5W11A91C_'+str(4-i)
        for name,source in x['reflection'].items():assert '/app-root/' in source['path'] and source['sha256']==x['current_source_manifest'][source['path'].split('/app-root/',1)[1]]
    normalized=lambda xs:[{k:v for k,v in row.items() if k not in ('log_id','user_id')} for row in xs]
    assert strict_equal(normalized(b['full_model_dto']),normalized(r['full_model_dto']))
    loss=[]
    for vector in r['native_width_vectors']:
        assert vector['php_type']=='double' and len(vector['binary64_hex'])==16
        f=struct.unpack('>d',bytes.fromhex(vector['binary64_hex']))[0]
        actual=fractions.Fraction.from_float(f);truth=int(vector['expected_exact_text'])
        if actual!=truth:loss.append({'exact_text':str(truth),'native_binary64_exact_integer':str(actual.numerator//actual.denominator),'native_json_literal':vector['json_literal']})
    assert len(loss)==3 and r['native_field_type_or_value_differences']==20
    assert old['exit_code']==1 and old['stderr_bytes']==0 and old['report']['status']=='expected_red' and old['report']['visible_width']==9 and old['report']['expected_width']==5
    for lane,prepared,account,pks in [('B',pb,ab,[340,341,342,343,344]),('C',pc['report'],ac['report'],[64,65,66,67,68])]:
        assert prepared['status']=='pass' and prepared['committed'] is True and prepared['successful_inserts']==prepared['insert_attempts']==5
        assert prepared['generated_small_exact_pks']==pks
        assert prepared['account_user_unchanged'] is True and prepared['full_row_cast_truth_verified'] is True
        assert prepared['before_user_sha256']==prepared['after_user_sha256']==account['own_user_sha256']
        assert prepared['business_updates']==prepared['business_deletes']==prepared['ddl']==prepared['api_writes']==0
        assert account['status']=='pass' and account['retained_marked_rows']==5 and account['own_user_and_balances_unchanged'] is True
    assert pc['exit_code']==pc['stderr_bytes']==ac['exit_code']==ac['stderr_bytes']==0
    assert r['orm_file_cache_cold_hot_equal'] is True and r['cursor_connection_clones']==b['cursor_clones']==1
    assert r['negative_boundaries']['float_getter']=='precision_loss_still_open'
    return loss
def main():
    files=['B-combo-final.json','C-combo-first.json','C-combo-old.json','prepared-B.json','prepared-C.json','account-after-B.json','account-after-C.json'];vals=[json.loads((P/f).read_text()) for f in files]
    loss=validate(*vals)
    mutations=[]
    for key,value in [('wide_above_2pow53_samples',0),('actual_prepares',9),('business_select_executions',13),('hot_additional_prepares',1),('global_p0_closed',True),('native_field_type_or_value_differences',0)]:
        a=copy.deepcopy(vals);a[1]['report'][key]=value;mutations.append(a)
    a=copy.deepcopy(vals);a[1]['report']['source_without_pk'][2]['biz_order_id']=9007199254740992;mutations.append(a)
    a=copy.deepcopy(vals);a[0]['source_without_pk'][2]['biz_order_id']=float(9007199254740993);mutations.append(a)
    a=copy.deepcopy(vals);a[3]['successful_inserts']=6;mutations.append(a)
    a=copy.deepcopy(vals);a[4]['report']['account_user_unchanged']=False;mutations.append(a)
    a=copy.deepcopy(vals);a[2]['report']['visible_width']=5;mutations.append(a)
    a=copy.deepcopy(vals);a[1]['report']['full_model_dto'][0]['biz_order_id']=18446744073709551615;mutations.append(a)
    for i,a in enumerate(mutations):
        try:validate(*a)
        except AssertionError:continue
        raise AssertionError('Mutation escaped '+str(i))
    print(json.dumps({'status':'pass','evidence_positive':1,'negative_mutations':len(mutations),'native_binary64_numeric_loss_cases':loss,'new_rows_retained':10,'preparation_inserts':10,'source_mysql_type_contract':'four PHP64 integers and UINT64max string','client_JS_Number_acceptance':'not tested; binary64 client limitation remains separate','business_reads':0,'dml':0},ensure_ascii=False,indent=2))
if __name__=='__main__':main()
