#!/usr/bin/env python3
"""Offline recomputation of the 12 real HTTPs and 32 own-row DML statements."""
import hashlib
import json
import re
from pathlib import Path

root=Path(__file__).parent
assert hashlib.sha256((root/'API-RETEST-PROPOSAL.md').read_bytes()).hexdigest()=='c5f73df6e3de4d7d0dc44d471c9ecd85b3903301257cbd530082a17656bc13c3'
private=root/'api-private-evidence'
marker='CDXMATSC261011'
summary={'passed':True,'HTTP':0,'DML':0,'DDL':0,'lanes':{},'API_types_equal':True,'noop_native_rowCount':{},'new_unique_interface_count':0}
read_types={}
for lane,old_count,reads in [('B',36,290),('C',5,314)]:
    directory=private/lane
    def read(name):return json.loads((directory/name).read_text())
    baseline=read('baseline.json')
    assert baseline==read('expected-baseline.json')
    assert len(baseline['material_category'])==old_count
    final=read('final.json');lease=final['lease'];ids={str(row['id']) for row in lease}
    assert len(ids)==len(lease)==3
    role={row['name'].split(marker+'-')[1]:str(row['id']) for row in lease}
    assert set(role)=={'P1','P2','L'}
    def guard(state):
        assert [row for row in state['material_category'] if str(row['id']) not in ids]==baseline['material_category']
        assert state['nutrition_category']==baseline['nutrition_category']
        for name in ('staff','user','actor'):assert state[name+'_sha256']==baseline[name+'_sha256']
    def own(state,suffix):
        found=[row for row in state['material_category'] if row['name']==marker+'-'+suffix]
        assert len(found)==1
        return found[0]
    def seconds(value):
        assert isinstance(value,str) and re.fullmatch(r'\d{4}-\d\d-\d\d \d\d:\d\d:\d\d',value)
        return value
    dml=0;server_reads=0;wires={}
    for step,wanted_dml in [('A1',1),('A2',1),('A3',2),('A4',3),('A5',0),('A6',1)]:
        wire=read(step+'.wire.json');result=read(step+'.result.json');sent=read(step+'.sent.json');wires[step]=wire
        assert sent['path']==wire['path'] and sent['params']==wire['params']
        assert wire['HTTP']==result['HTTP']==200
        assert wire['body']==result['body'] and hashlib.sha256(wire['body'].encode()).hexdigest()==wire['body_sha256']==result['body_sha256']
        body=json.loads(result['body']);assert body['code']==0 and type(body['code']) is int and isinstance(body['message'],str) and isinstance(body['data'],(list,dict))
        assert result['client_before']==wire['source_before'] and result['independent_after']==wire['source_after']
        for state in (wire['source_before'],wire['source_after']):guard(state)
        assert wire['remaining_allowances']==[]
        actual=[entry for entry in wire['prepared_SQL'] if re.match(r'^\s*(INSERT|UPDATE|DELETE)\b',entry['queryString'],re.I)]
        assert len(actual)==wanted_dml==result['DML']
        for entry in actual:
            assert entry['ok'] and 'material_category' in entry['queryString'].lower()
        dml+=len(actual);server_reads+=wire['SELECT']
        if step in ('A1','A2','A3'):
            row=own(wire['source_after'],{'A1':'P1','A2':'P2','A3':'L'}[step])
            assert set(row)=={'id','pid','name','category_path','level','status','leaf_flag','type','is_delete','update_time','create_time','create_by'}
            assert row['create_by']=='admin'
            for field in ('create_time','update_time'):
                assert result['clock_before']<=seconds(row[field])<=result['clock_after']
            assert str(row['status'])==str(row['leaf_flag'])==str(row['type'])=='1' and str(row['is_delete'])=='0'
            assert str(row['pid'])==(role['P1'] if step=='A3' else '0')
            assert row['category_path']==(role['P1'] if step=='A3' else '')
            assert str(row['level'])==('2' if step=='A3' else '1')
        if step in ('A3','A4','A6'):
            for suffix in (['P1'] if step=='A3' else ['P1','P2','L-U']):
                row=own(wire['source_after'],suffix)
                assert result['clock_before']<=seconds(row['update_time'])<=result['clock_after']
                before_rows=[r for r in wire['source_before']['material_category'] if str(r['id'])==str(row['id'])]
                assert len(before_rows)==1 and before_rows[0]['update_time']!=row['update_time']
        if step=='A5':
            data=body['data'];assert isinstance(data,dict) and data['pname']==marker+'-P2'
            assert str(data['id'])==role['L'] and str(data['pid'])==data['category_path']==role['P2']
            for field in ('create_time','update_time'):seconds(data[field])
            read_types[lane]={key:type(value).__name__ for key,value in data.items()}
        if step=='A6':
            for suffix in ('P1','P2','L-U'):assert str(own(wire['source_after'],suffix)['is_delete'])=='1'
    for step in ['D'+str(i) for i in range(1,9)]:
        start=read(step+'.started.json');result=read(step+'.result.json')
        assert result['SQL']==start['SQL'] and result['bindings']==start['bindings'] and result['source_before']==start['source_before']
        guard(result['source_before']);guard(result['source_after'])
        assert result['SQL'].startswith('UPDATE ') and 'material_category' in result['SQL']
        dml+=1
        before=result['source_before'];after=result['source_after']
        if step=='D6':
            assert after==before
            assert result['rowCount']==(0 if lane=='B' else 1)
            assert own(after,'P2')['update_time']=='2001-01-01 01:01:01'
            summary['noop_native_rowCount'][lane]=result['rowCount']
        elif step=='D7':
            assert str(own(after,'P2')['status'])=='0' and own(after,'P2')['update_time']=='2002-02-02 02:02:02'
        elif step=='D8':
            assert str(own(after,'P2')['status'])=='1' and own(after,'P2')['update_time']=='2002-02-02 02:02:02'
        else:
            affected=ids if step=='D5' else {role[{'D1':'P1','D2':'P1','D3':'P2','D4':'L'}[step]]}
            for old,new in zip(before['material_category'],after['material_category']):
                expected=dict(old)
                if str(old['id']) in affected:expected['update_time']='2001-01-01 01:01:01'
                assert new==expected
    closure=read('del-closure.json')
    assert set(map(str,closure['own_ids']))==set(map(str,closure['original_closure']))==ids and closure['reference_count']==0
    guard(final['source'])
    assert final['HTTP']==6 and final['DML']==dml==16 and final['SELECT']==reads<=320
    assert len(read('client-readonly-SQL.json'))+server_reads==reads
    assert len(final['source']['material_category'])==old_count+3
    assert all(str(row['is_delete'])=='1' for row in final['source']['material_category'] if str(row['id']) in ids)
    assert read('preflight.json')['creator_sha256']=='8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918'
    summary['HTTP']+=6;summary['DML']+=16
    summary['lanes'][lane]={'HTTP':6,'DML':16,'SELECT':reads,'old_material_rows':old_count,'final_material_rows':old_count+3,'new_own3_softdeleted':True,'old_source_exact':True,'defaults_and_parent_move_delete_clock_advance':True,'explicit_timestamp_and_self_preserved':True,'actual_HTTP_read_time_string_bytes':19,'lease_ids':sorted(ids,key=int)}
assert read_types['B']==read_types['C']
summary['API_read_field_types']=read_types['B']
print(json.dumps(summary,ensure_ascii=False,indent=2))
