from __future__ import annotations
import argparse
import json
from pathlib import Path
HARD_CLASSES = ('Syn', 'UDPLag', 'DrDoS_NTP')

def _load_pc(run_dir: Path) -> dict | None:
    p = run_dir / 'per_class.json'
    if not p.exists():
        print(f'[warn] missing {p}')
        return None
    return json.loads(p.read_text())['terminal_norm']

def _render_block(title: str, data: list[dict], hard_classes: tuple[str, ...]) -> list[str]:
    lines: list[str] = []
    lines.append('')
    lines.append('=' * 96)
    lines.append(f'# {title}')
    lines.append('=' * 96)
    lines.append(f"{'experiment':<40s}  {'overall AUROC':>14s}  {'macro AUROC':>14s}  {'TPR@1%FPR':>12s}  {'FPR@95%TPR':>12s}")
    lines.append('-' * 96)
    if not data:
        lines.append('(no results)')
        return lines
    base = data[0]['pc']
    for d in data:
        pc = d['pc']
        delta_overall = pc['overall_auroc'] - base['overall_auroc']
        delta_macro = pc['macro_auroc'] - base['macro_auroc']
        delta_tpr = pc['tpr_at_1fpr'] - base['tpr_at_1fpr']
        lines.append(f"{d['label']:<40s}  {pc['overall_auroc']:>8.4f} ({delta_overall:+.4f})  {pc['macro_auroc']:>8.4f} ({delta_macro:+.4f})  {pc['tpr_at_1fpr']:>6.4f} ({delta_tpr:+.4f})  {pc['fpr_at_95tpr']:>12.4f}")
    if hard_classes:
        lines.append('')
        lines.append(f"--- focus classes: {', '.join(hard_classes)} ---")
        for c in hard_classes:
            row = f'{c:<18s}'
            for d in data:
                pc = d['pc']
                match = next((r for r in pc['per_class'] if r['class'] == c), None)
                if match is None:
                    row += f" {d['tag']}:n/a"
                else:
                    row += f" {d['tag']}:{match['auroc']:.3f}(tpr={match['tpr_at_1fpr']:.3f})"
            lines.append(row)
    lines.append('')
    lines.append('--- all classes (sorted by E0 AUROC ascending) ---')
    base_pc = data[0]['pc']['per_class']
    ordered = sorted(base_pc, key=lambda r: r['auroc'])
    hdr2 = f"{'class':<22s} {'N':>8s}" + ''.join((f" {d['tag']:>14s}" for d in data))
    lines.append(hdr2)
    for row_b in ordered:
        cls = row_b['class']
        row = f"{cls:<22s} {row_b['n']:>8d}"
        for d in data:
            pc = d['pc']
            match = next((r for r in pc['per_class'] if r['class'] == cls), None)
            row += f" {match['auroc']:>14.4f}" if match else f" {'—':>14s}"
        lines.append(row)
    return lines

def main():
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument('--root', type=Path, required=True)
    ap.add_argument('--with-iscxtor', action='store_true', help='Also load iscxtor_eval/per_class.json under each tag and render a second comparison block for the CICIDS2017 → ISCXTor2016 transfer target.')
    args = ap.parse_args()
    runs = [('E0 baseline (mixed_dequant)', 'e0_baseline'), ('E1 relative_v2 (channel rehab)', 'e1_relv2'), ('E2 relative_v2 + 8-d context', 'e2_relv2_ctx')]
    ddos_data: list[dict] = []
    for (label, tag) in runs:
        pc = _load_pc(args.root / tag)
        if pc is not None:
            ddos_data.append({'label': label, 'tag': tag, 'pc': pc})
    iscx_data: list[dict] = []
    if args.with_iscxtor:
        for (label, tag) in runs:
            pc = _load_pc(args.root / tag / 'iscxtor_eval')
            if pc is not None:
                iscx_data.append({'label': label, 'tag': tag, 'pc': pc})
    if not ddos_data and (not iscx_data):
        print('[err] no results found under', args.root)
        return
    lines: list[str] = []
    if ddos_data:
        lines.extend(_render_block('CICIDS2017 → CICDDoS2019 (target=DDoS attacks; benign=normal)', ddos_data, HARD_CLASSES))
    if iscx_data:
        lines.extend(_render_block('CICIDS2017 → ISCXTor2016 (target=Tor flows; benign=nontor)', iscx_data, ()))
    txt = '\n'.join(lines)
    print(txt)
    (args.root / 'summary.txt').write_text(txt + '\n')
    combined = {'cicddos2019': ddos_data, 'iscxtor2016': iscx_data}
    (args.root / 'summary.json').write_text(json.dumps(combined, indent=2))
    print(f"\n[saved] {args.root / 'summary.txt'}")
if __name__ == '__main__':
    main()
