#!/usr/bin/env python3
"""Strict Help CRUD comparison. Only audited owned fixture identities/times map."""
import argparse
from copy import deepcopy
from datetime import datetime
import importlib.util
import json
from pathlib import Path
import re
from zoneinfo import ZoneInfo

parser=argparse.ArgumentParser()
parser.add_argument('b_runtime')
parser.add_argument('c_runtime')
args=parser.parse_args()
module=Path(__file__).resolve().parents[3]/'work/2026-09-28-smart-canteen-dm8-poc/http-harness/strict_response_compare.py'
spec=importlib.util.spec_from_file_location('strict',module)
strict=importlib.util.module_from_spec(spec)
spec.loader.exec_module(strict)
b_report,b_raw,b_map=[strict.load_json(Path(args.b_runtime)/f) for f in ['report.json','responses.json','fixture_map.json']]
c_report,c_raw,c_map=[strict.load_json(Path(args.c_runtime)/f) for f in ['report.json','responses.json','fixture_map.json']]
expected={f'{module}_{size}_{action}' for module in ['STORE','TERMINAL']
    for size in ['EMPTY','QUOTED','NORMAL','BYTES_32767','BYTES_32768','BYTES_32769','BYTES_49152']
    for action in ['ADD','LIST','EDIT','LIST_EDITED','DELETE','LIST_DELETED']}
if set(b_raw)!=expected or set(c_raw)!=expected:
    raise SystemExit('Expected 84 scenarios absent')
if b_report['source_sha256']!=c_report['source_sha256']:
    raise SystemExit('Business source differs')
for report in [b_report,c_report]:
    if report['failures'] or not report['natural_unchanged'] or report['independent_remaining'] or not report['token_cleaned']:
        raise SystemExit('Source/audit/cleanup preconditions failed')
rule={'$["trace_id"]':{'type':'string','pattern':'[0-9a-f]{13}',
    'reason':'Validated framework-generated per-request trace','require_distinct':True}}

def normalize_owned(name, body, mapping):
    result=deepcopy(body)
    if name not in mapping['source_rows']:
        return result,None
    source=mapping['source_rows'][name]
    rows=result['data']['list']['data']
    owned=[r for r in rows if strict.kind(r.get('help_id'))=='number'
        and r.get('help_id')==int(source['help_id'])]
    if len(owned)!=1:
        raise ValueError('Owned source projection missing: '+name)
    row=owned[0]
    if strict.kind(row['help_id'])!='number' or row['title']!=source['title']:
        raise ValueError('Owned identifier/title not independently verified')
    for field in ['create_time','update_time']:
        value=datetime.fromtimestamp(int(source[field]),ZoneInfo('Asia/Shanghai')).strftime('%Y-%m-%d %H:%M:%S')
        if row[field]!=value or strict.kind(row[field])!='string':
            raise ValueError('Owned timestamp does not match source: '+field)
        row[field]='2000-01-01 00:00:00'
    if not row['title'].startswith(mapping['marker']):
        raise ValueError('Own marker absent')
    row['title']='<OWN_HELP>'+row['title'][len(mapping['marker']):]
    row['help_id']=0
    if strict.kind(row['content'])!='string':
        raise ValueError('TEXT/CLOB did not return a JSON string')
    return result,row

cases=[]
for name in sorted(expected):
    left,right=b_raw[name],c_raw[name]
    for side in [left,right]:
        if side['http_status']!=200 or side['body'].get('status')!=200:
            raise SystemExit('Positive HTTP/business outcome absent: '+name)
        if re.fullmatch('[0-9a-f]{13}',side['body'].get('trace_id','')) is None:
            raise SystemExit('Trace malformed')
    lb,lo=normalize_owned(name,left['body'],b_map)
    rb,ro=normalize_owned(name,right['body'],c_map)
    diffs=strict.compare(lb,rb,rules=rule)
    own_diffs=strict.compare(lo,ro) if lo is not None and ro is not None else []
    bcase=next(x for x in b_report['cases'] if x['scenario']==name)
    ccase=next(x for x in c_report['cases'] if x['scenario']==name)
    if any(bcase.get(key)!=ccase.get(key) for key in ['content_bytes','content_sha256','method','path']):
        raise SystemExit('Input bytes/hash or HTTP method/path differs: '+name)
    cases.append({'scenario':name,'method':bcase['method'],'path':bcase['path'],
        'source_equal_both':bcase['source_equal'] and ccase['source_equal'],
        'request_content_bytes':bcase.get('content_bytes'),
        'request_content_sha256':bcase.get('content_sha256'),
        'full_response_differences':len(diffs),'full_response_type_differences':sum(l!=r for _,l,r,_,_ in diffs),
        'difference_paths_and_types':[{'path':p,'mysql_type':l,'dm_type':r} for p,l,r,_,_ in diffs],
        'owned_record_compared':lo is not None,'owned_record_differences':len(own_diffs)})

def runtime_summary(report):
    events=[e for e in report['events'] if e.get('kind')=='query_observed']
    flags={k:sum(e.get(k,0) for e in report['events'] if e.get('kind')=='listener_classification')
        for k in ['connection_events','metadata_events','unclassified_events']}
    return {'queries':len(events),'metadata_classification':flags,
        'lexical_warnings':sum(bool(e.get('lexical_warning')) for e in events),
        'operation_counts':{op:sum(e['operation']==op for e in events) for op in sorted({e['operation'] for e in events})},
        'unique_fingerprints':len({e['fingerprint'] for e in events}),
        'callsites':sorted({site for e in events for site in e['callsites']})}
result={'date':'2026-10-10','baseline':b_report['baseline'],'scenario_pairs':84,'exact_method_path_keys':8,
    'positive_source_cases':sum(x['source_equal_both'] for x in cases),
    'full_response_strict_equal':sum(x['full_response_differences']==0 for x in cases),
    'owned_record_comparisons':sum(x['owned_record_compared'] for x in cases),
    'owned_record_differences':sum(x['owned_record_differences'] for x in cases),
    'cases':cases,'sources':b_report['source_sha256'],
    'schema':{'B':b_report['schema'],'C':c_report['schema']},
    'audit':{lane:{k:report[k] for k in ['natural_before','natural_after','natural_unchanged',
        'owned_records_physically_cleaned','independent_remaining','token_cleaned','trigger_count']}
        for lane,report in [('B',b_report),('C',c_report)]},
    'runtime':{'B':runtime_summary(b_report),'C':runtime_summary(c_report)},
    'normalization':'Only source-verified owned help_id/title marker/create_time/update_time and validated distinct trace; no content/type/length normalization',
    'scope':'Specified marked-document CRUD and content boundaries, not full interface or system acceptance'}
print(json.dumps(result,indent=2,ensure_ascii=False,default=str))
raise SystemExit(any(x['owned_record_differences'] or x['full_response_differences'] for x in cases))
