#!/usr/bin/env python3
"""Pure raw-bit verification. Preserve the four FLOAT53 point reds and aggregate red."""
import copy
import hashlib
import json
from pathlib import Path

ROOT = Path(__file__).resolve().parent
PLAN = json.loads((ROOT/'C-real-plan.json').read_bytes())
RAW = ROOT/'private-evidence/C-run.jsonl'


def verify(events):
    checks = 0
    def check(condition):
        nonlocal checks
        assert condition
        checks += 1
    check(len(events) == 321)
    check([e['seq'] for e in events] == list(range(1, 322)))
    check(events[0]['event'] == 'runtime' and events[-1]['event'] == 'released')
    check(events[0]['data']['runner_sha256'] == '63d545fe7e0712ce10b82d09d6443f6d4d88708f81337482aa86197b63b960d4')
    check(events[0]['data']['plan_sha256'] == '82cce0877e1b419d4c1c64dddc11e27d568d4cc13baca3b044e87de968e32300')
    check(not any(e['event'] in ('stopped', 'INSERT-unknown', 'DDL-unknown') for e in events))
    starts = [e['data'] for e in events if e['event'] == 'INSERT-start']
    check(len(starts) == 20 and sum(e['event'] == 'INSERT-success' for e in events) == 20)
    check(sum(e['event'] == 'DDL-success' for e in events) == 2)
    check([e['data']['sql'] for e in events if e['event'] == 'DDL-success'] == [PLAN['sql']['create'], PLAN['sql']['drop']])
    check(sum(e['event'] == 'rollback' for e in events) == 18)
    check([e['data']['count'] for e in events if e['event'] == 'rollback'] == list(range(1, 19)))
    read_sql = set(PLAN['identity'] + [PLAN['collision']] + PLAN['catalog'] + [v for k,v in PLAN['sql'].items() if k not in ('create', 'drop', 'insert', 'null_insert')])
    for e in events:
        if e['event'] in ('read-start', 'read-success', 'prepare-success'): check(e['data']['sql'] in read_sql)
    reads = [e for e in events if e['event'] == 'read-success']
    check(len(reads) == 106 and sum(e['event'] == 'prepare-success' for e in events) == 6)
    catalog = [e['data']['typed_rows'] for e in reads if e['data']['sql'] in PLAN['catalog']]
    check(catalog[:6] == [[]]*6)
    check(catalog[6:13] == catalog[13:20] == catalog[20:27])
    check(catalog[7] == [{'index_name': {'type': 'string', 'value': 'INDEX33556696'}, 'uniqueness': {'type': 'string', 'value': 'NONUNIQUE'}, 'index_type': {'type': 'string', 'value': 'CLUSTER'}}])
    check(all(x == [] for x in catalog[8:12]))
    point_map = {p['id']+'-'+m['id']: p for p in PLAN['points'] for m in PLAN['point_modes']}
    agg_map = {p['id']:p for p in PLAN['aggregates']}
    cases = {}; current = None
    for e in events:
        if e['event'] == 'case-start':
            current = e['data']['case']; check(current not in cases); cases[current] = []
        if e['event'] == 'read-success' and current is not None: cases[current].append(e['data'])
        if e['event'] == 'case-complete': check(current == e['data']['case']); current = None
    check(set(cases) == set(point_map) | set(agg_map))
    point_red = []
    for case,p in point_map.items():
        group = cases[case]; check(len(group) == 4)
        for data,key in zip(group, ['native_point','computed_point','render_point','count']): check(data['sql'] == PLAN['sql'][key])
        native = group[0]['typed_rows'][0]; computed = group[1]['typed_rows'][0]
        expected = p['stored_hex']
        for value in [native['r'],computed['power']]:
            if expected is None: check(value == {'type':'NULL','value':None})
            else: check(value['type'] == 'double' and value['hex_le'] == expected)
        bad = p['id'] in ('ORACLE-40','ORACLE-01')
        for value in [native['f'],computed['float_power']]:
            if expected is None: check(value == {'type':'NULL','value':None})
            else: check(value['type'] == 'double' and value['hex_le'] == ('01'+expected[2:] if bad else expected))
        if bad: point_red.append(case)
        check(group[-1]['typed_rows'] == [{'n':{'type':'integer','value':0}}])
    aggregate_red = []
    for case,p in agg_map.items():
        group = cases[case];check(len(group) == 4)
        for data,key in zip(group,['native_rows','aggregate','aggregate_render','count']): check(data['sql'] == PLAN['sql'][key])
        v = group[1]['typed_rows'][0];check(v['n'] == {'type':'integer','value':2})
        check(v['power']['hex_le'] == p['SUM_hex'] and v['average_value']['hex_le'] == p['AVG_hex'])
        bad = case == 'OS-A1'
        check(v['float_power']['hex_le'] == ('01'+p['SUM_hex'][2:] if bad else p['SUM_hex']))
        check(v['float_average']['hex_le'] == ('01'+p['AVG_hex'][2:] if bad else p['AVG_hex']))
        if bad: aggregate_red.append(case)
        check(group[-1]['typed_rows'] == [{'n':{'type':'integer','value':0}}])
    check(point_red == ['ORACLE-40-S','ORACLE-40-OS','ORACLE-01-S','ORACLE-01-OS'])
    check(aggregate_red == ['OS-A1'])
    complete = [e['data'] for e in events if e['event'] == 'complete']
    check(complete == [{'DDL':2,'INSERT':20,'rollbacks':18,'diagnostics':112,'object0':True,'natural_queries':0,'HTTP':0,'C_warning_status':'unknown'}])
    check(events[-2]['event'] == 'complete')
    check([e['data'] for e in events if e['event'] == 'cleanup-gate'][0]['row0_both'] is True)
    return checks,point_red,aggregate_red


def main():
    events = [json.loads(line) for line in RAW.read_bytes().splitlines()]
    assertions,point_red,aggregate_red = verify(events)
    mutants=[]
    def mutation(event,change):
        x=copy.deepcopy(events);target=next(e for e in x if event(e));change(target);mutants.append(x)
    mutation(lambda e:e['event']=='read-success' and e['data']['sql']==PLAN['sql']['computed_point'], lambda e:e['data']['typed_rows'][0]['power'].update(hex_le='0000000000000000'))
    mutation(lambda e:e['event']=='read-success' and e['data']['sql']==PLAN['sql']['aggregate'], lambda e:e['data']['typed_rows'][0]['float_power'].update(hex_le=PLAN['aggregates'][0]['SUM_hex']))
    mutation(lambda e:e['event']=='read-start',lambda e:e['data'].update(sql='SELECT * FROM YDY_NUTRITION'))
    mutation(lambda e:e['event']=='INSERT-success',lambda e:e.update(event='INSERT-start'))
    mutation(lambda e:e['event']=='rollback',lambda e:e['data'].update(count=18))
    mutation(lambda e:e['event']=='complete',lambda e:e['data'].update(object0=False))
    x=copy.deepcopy(events);x[-1]['seq']=1;mutants.append(x)
    mutation(lambda e:e['event']=='read-success' and e['data']['sql']==PLAN['catalog'][1] and e['data']['typed_rows']!=[],lambda e:e['data']['typed_rows'][0]['index_name'].update(value='INDEX33556697'))
    rejected=0
    for x in mutants:
        try:verify(x)
        except (AssertionError,KeyError,IndexError):rejected+=1
    assert rejected==len(mutants)
    print(json.dumps({'status':'REAL16_POINTS_AND2_AGGREGATES_MATCH_B_BITS_FLOAT53_REDS_PRESERVED','assertions':assertions,'mutants_rejected':rejected,'raw_sha256':hashlib.sha256(RAW.read_bytes()).hexdigest(),'events':len(events),'REAL_points':16,'REAL_aggregates':2,'FLOAT53_point_reds':point_red,'FLOAT53_aggregate_reds':aggregate_red,'DDL':2,'INSERT':20,'rollbacks':18,'diagnostics':112,'object0':True,'natural':0,'HTTP':0},indent=2))


if __name__=='__main__':main()
