#!/usr/bin/env python3
"""Verify GDR persistent packet session trace against its config."""
from __future__ import annotations

import argparse, csv, hashlib, json, re
from pathlib import Path
from typing import Any


def parse_hex(text: str | None) -> list[int]:
    if not text: return []
    out=[]
    for tok in str(text).replace(',', ' ').split():
        if tok == '...': break
        try: out.append(int(tok,16)&0xFF)
        except Exception:
            try: out.append(int(tok,0)&0xFF)
            except Exception: pass
    return out


def row_val(row: dict[str,str]) -> int | None:
    bh=(row.get('bytes_hex') or row.get('bytes') or '').strip().split()
    if bh:
        try: return int(bh[0],16)&0xFF
        except Exception: pass
    v=row.get('value')
    if v:
        try: return int(v,0)&0xFF
        except Exception: pass
    return None


def load_commands(config_path: Path) -> list[dict[str,Any]]:
    cfg=json.loads(config_path.read_text(encoding='utf-8'))
    model=(cfg.get('trace') or {}).get('gdr_packet_session_model') or cfg.get('gdr_packet_session_model') or {}
    cmds=[]
    for i,c in enumerate(model.get('commands') or []):
        cmds.append({
            'index': i,
            'tag': str(c.get('tag', f'cmd{i:02d}')),
            'direction': str(c.get('direction','')).lower(),
            'cdb': parse_hex(c.get('cdb_hex')) or [int(str(x),0)&0xFF for x in c.get('cdb',[])],
            'data_in': parse_hex(c.get('data_in_hex')),
            'data_out': parse_hex(c.get('data_out_hex')),
        })
    return cmds


def main() -> int:
    ap=argparse.ArgumentParser(description='Verify GDR persistent multi-command packet session')
    ap.add_argument('trace_csv')
    ap.add_argument('--config', required=True)
    ap.add_argument('--out', required=True)
    ns=ap.parse_args()
    trace_path=Path(ns.trace_csv)
    cmds=load_commands(Path(ns.config))
    events=[]
    data_in={i:[] for i in range(len(cmds))}
    data_out={i:[] for i in range(len(cmds))}
    arms=[]; comps=[]; timers=[]
    idx_re=re.compile(r'index=(\d+)')
    with trace_path.open(newline='', encoding='utf-8', errors='replace') as fh:
        for row in csv.DictReader(fh):
            ev=row.get('event','')
            notes=row.get('notes','') or ''
            m=idx_re.search(notes)
            idx=int(m.group(1)) if m else None
            if ev.startswith('gdr_session') or ev in ('gdr_data_in_ready','gdr_data_in_submit'):
                events.append(row)
            if ev=='gdr_session_command_arm': arms.append(row)
            elif ev=='gdr_session_timer_done': timers.append(row)
            elif ev=='gdr_session_command_complete': comps.append(row)
            elif ev=='gdr_session_data_in_source' and idx is not None:
                v=row_val(row)
                if v is not None: data_in.setdefault(idx,[]).append(v)
            elif ev=='gdr_session_data_out_read' and idx is not None:
                v=row_val(row)
                if v is not None: data_out.setdefault(idx,[]).append(v)
    lines=[]
    lines.append('# GDR persistent packet session verification')
    lines.append('')
    lines.append(f'trace={trace_path.name}')
    lines.append(f'commands_expected={len(cmds)}')
    lines.append(f'commands_armed={len(arms)}')
    lines.append(f'timer_done_events={len(timers)}')
    lines.append(f'commands_completed={len(comps)}')
    exact_all=True
    lines.append('')
    lines.append('| index | tag | direction | expected_len | observed_len | exact | sha256 |')
    lines.append('|---:|---|---|---:|---:|---|---|')
    for cmd in cmds:
        i=cmd['index']; direction=cmd['direction']
        if direction=='in':
            exp=cmd.get('data_in') or []
            obs=(data_in.get(i) or [])[:len(exp)]
        elif direction=='out':
            exp=cmd.get('data_out') or []
            obs=(data_out.get(i) or [])[:len(exp)]
        else:
            exp=[]; obs=[]
        exact=(obs==exp)
        if exp and not exact: exact_all=False
        sh=hashlib.sha256(bytes(obs)).hexdigest() if obs else ''
        lines.append(f"| {i} | `{cmd['tag']}` | {direction} | {len(exp)} | {len(obs)} | {str(exact).lower()} | `{sh}` |")
        if exp and not exact:
            off=0
            while off < min(len(exp), len(obs)) and exp[off]==obs[off]: off+=1
            got='EOF' if off>=len(obs) else f'0x{obs[off]:02X}'
            want=f'0x{exp[off]:02X}' if off < len(exp) else 'EOF'
            lines.append(f"  - first_mismatch[{i}] offset={off} actual={got} expected={want}")
    lines.append('')
    lines.append(f'exact_complete_session={str(exact_all and len(comps) >= len(cmds)).lower()}')
    Path(ns.out).write_text('\n'.join(lines)+'\n', encoding='utf-8')
    print(f'[OK] wrote {ns.out}')
    if not (exact_all and len(comps) >= len(cmds)):
        raise SystemExit(2)
    return 0

if __name__=='__main__':
    raise SystemExit(main())
