#!/usr/bin/env python3
"""Strict packets.raw multiset compare across positions 1-4.

Game Index = byte at offset 5 in SOH..EOP frames (0-15).
Slave requests = 0x20,0x30,0x40,0x50,0x60,0x70,0x80.
"""
from __future__ import annotations

import argparse
from collections import Counter
from pathlib import Path

from fixture_pc_paths import resolve_capture_file

REQUEST = {0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80}
RESPONSE = {0x29, 0x39, 0x49, 0x59, 0x69, 0x79, 0x89}
ACK = {0x21, 0x31, 0x41, 0x51, 0x61, 0x71, 0x81}
PLAY = REQUEST | RESPONSE | ACK | {0x0D}


def tid_le(raw: bytes, off: int = 3) -> str | None:
    if off + 1 >= len(raw):
        return None
    return f"{raw[off + 1]:02X}{raw[off]:02X}"


def game_id_hex(raw: bytes) -> str | None:
    if len(raw) >= 7:
        return f"{raw[6]:02X}{raw[5]:02X}"
    return None


def load_raw_lines(root: Path, pos: int) -> Counter:
    p = resolve_capture_file(root, pos, "packets.raw")
    if p is None:
        return Counter()
    return Counter(line.strip() for line in p.read_text().splitlines() if line.strip())


def load_eth_rx_keys(root: Path, pos: int) -> Counter:
    import json

    p = resolve_capture_file(root, pos, "events.jsonl")
    if p is None:
        return Counter()
    c: Counter = Counter()
    for line in p.read_text().splitlines():
        if not line.strip():
            continue
        row = json.loads(line)
        if row.get("ev") != "eth_rx":
            continue
        msg = row.get("msg")
        if not msg:
            continue
        try:
            msg_i = int(msg, 16)
        except ValueError:
            continue
        if msg_i not in PLAY:
            continue
        c[(msg, row.get("tid"), row.get("len"))] += 1
    return c


def parse_line(line: str) -> bytes | None:
    try:
        return bytes(int(x, 16) for x in line.split(","))
    except ValueError:
        return None


def wager_summary(root: Path, pos: int) -> Counter:
    c: Counter = Counter()
    for line, n in load_raw_lines(root, pos).items():
        raw = parse_line(line)
        if not raw or len(raw) < 6 or raw[0] != 0x05 or raw[-1] != 0x0D:
            continue
        msg = raw[1]
        if msg not in REQUEST:
            continue
        tid = tid_le(raw)
        gi = raw[5]
        c[(tid, gi, f"{msg:02X}")] += n
    return c


def compare_counters(ref: Counter, other: Counter, ref_label: str, other_label: str) -> list[str]:
    lines: list[str] = []
    missing = ref - other
    extra = other - ref
    if not missing and not extra:
        lines.append(f"  {other_label}: EXACT match ({sum(ref.values())} lines)")
        return lines
    lines.append(f"  {other_label}: MISMATCH vs {ref_label}")
    for k, n in sorted(missing.items(), key=str):
        lines.append(f"    MISSING x{n}: {k}")
    for k, n in sorted(extra.items(), key=str):
        lines.append(f"    EXTRA   x{n}: {k}")
    return lines


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("root", type=Path)
    parser.add_argument("-o", "--output", type=Path)
    args = parser.parse_args()
    root = args.root.resolve()

    lines = ["=" * 72, "STRICT PACKETS.RAW COMPARE (pos 1 = copper reference)", f"Root: {root}", "=" * 72, ""]

    h1 = load_raw_lines(root, 1)
    total_wagers = sum(wager_summary(root, 1).values())
    lines.append(f"Pos 1 slave-request count (0xX0 in packets.raw): {total_wagers}")
    lines.append("")
    lines.append("Wagers by (tid, game_index byte[5], msg):")
    for pos in (1, 2, 3):
        ws = wager_summary(root, pos)
        lines.append(f"  Pos {pos}: {sum(ws.values())} requests")
        for k, n in sorted(ws.items()):
            lines.append(f"    tid={k[0]} game_index={k[1]} msg=0x{k[2]} x{n}")
    lines.append("")
    lines.append("## Exact packets.raw line multiset (every byte on local POG422 segment)")
    for pos in (2, 3, 4):
        lines.extend(compare_counters(h1, load_raw_lines(root, pos), "pos 1 trunk", f"pos {pos}"))

    lines.append("")
    lines.append("Note: pos 4 is Pi-only on copper but must see the same bank traffic as pos 1")
    lines.append("(T340+ segment + bridge inject). EXTRA vs pos 1 = capture or routing defect.")

    report = "\n".join(lines) + "\n"
    print(report, end="")
    if args.output:
        args.output.write_text(report, encoding="utf-8")
    return 0


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