#!/usr/bin/env python3
"""Find pos4 end-of-soak MRT fingerprint + witness copper."""
from __future__ import annotations

import json
from collections import Counter, defaultdict
from datetime import datetime, timedelta
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1] / "logs"
TID = "37BC"


def parse_utc(u: str) -> datetime:
    return datetime.fromisoformat(u.replace("Z", "+00:00"))


def iter_events(pos: int):
    d = ROOT / str(pos) / "data"
    for f in d.glob("events.jsonl*"):
        with f.open("r", encoding="utf-8", errors="replace") as fh:
            for line in fh:
                line = line.strip()
                if not line:
                    continue
                try:
                    e = json.loads(line)
                except Exception:
                    continue
                e["_file"] = f.name
                yield e


def opcode(e) -> str | None:
    op = e.get("opcode")
    if op is None:
        return None
    if isinstance(op, int):
        return f"{op:02X}"
    s = str(op).upper().replace("0X", "")
    return s.zfill(2)[-2:]


def main() -> None:
    # Load pos4 gameplay markers sorted by utc
    marks = []
    for e in iter_events(4):
        ev = e.get("ev") or ""
        u = e.get("utc")
        if not u:
            continue
        op = opcode(e)
        interesting = False
        kind = ev
        if ev == "packet_complete" and e.get("tid") == TID:
            interesting = True
            kind = f"pc_{op}"
        elif ev in (
            "status_0d_synth_done",
            "status_0d_synth_trigger",
            "status_0d_auto_close_miss",
            "status_0d_auto_close_pending",
            "mrt_risk_late_0d_after_x1",
            "mrt_risk_hand_reopen",
            "mrt_risk_hand_txn_restart",
            "local_wager",
            "hand_open",
            "hand_close",
        ):
            interesting = True
        elif ev.startswith("mrt_risk"):
            interesting = True
        if interesting:
            marks.append((u, kind, ev, op, e.get("tid"), e.get("game"), e["_file"], e))

    marks.sort(key=lambda x: x[0])
    print(f"pos4 marks={len(marks)} first={marks[0][0] if marks else None} last={marks[-1][0] if marks else None}")

    # Gaps > 60s between packet_complete for local TID
    pcs = [(parse_utc(u), kind, ev, op, f) for u, kind, ev, op, tid, g, f, e in marks if kind.startswith("pc_")]
    print("\n=== pos4 local TID packet_complete gaps >60s ===")
    for i in range(1, len(pcs)):
        dt = (pcs[i][0] - pcs[i - 1][0]).total_seconds()
        if dt > 60:
            print(
                f"  gap={dt:.0f}s  after {pcs[i-1][0].isoformat()} {pcs[i-1][1]} "
                f"-> before {pcs[i][0].isoformat()} {pcs[i][1]}  file_after={pcs[i][4]}"
            )

    # Last 90 minutes of marks (relative to last mark)
    if not marks:
        return
    end = parse_utc(marks[-1][0])
    start_win = end - timedelta(minutes=90)
    print(f"\n=== last 90 min marks (from {start_win.isoformat()}) ===")
    last90 = [m for m in marks if parse_utc(m[0]) >= start_win]
    # compress: show only non-hs heavy kinds, sample
    show = [
        m
        for m in last90
        if m[2]
        in (
            "status_0d_synth_done",
            "status_0d_auto_close_miss",
            "status_0d_auto_close_pending",
            "mrt_risk_late_0d_after_x1",
            "mrt_risk_hand_reopen",
            "mrt_risk_hand_txn_restart",
            "mrt_risk_txn_restart_skip",
            "mrt_risk_arm_skip",
            "local_wager",
            "hand_open",
            "hand_close",
        )
        or (m[1].startswith("pc_") and m[3] in ("40", "80", "C0", "41", "81", "C1", "0D", "0A", "69", "61", "70", "71", "79"))
    ]
    print(f"filtered={len(show)}")
    for m in show[-80:]:
        print(f"  {m[0]}  {m[1]}  game={m[5]}  file={m[6]}")

    # status_0d_auto_close_miss detail + nearby
    print("\n=== status_0d_auto_close_miss / late_0d full detail ===")
    for m in marks:
        if m[2] in ("status_0d_auto_close_miss", "mrt_risk_late_0d_after_x1"):
            e = m[7]
            print(json.dumps({k: v for k, v in e.items() if not str(k).startswith("_")}, default=str)[:1000])
            t0 = parse_utc(m[0])
            # nearby gameplay pcs +/- 30s
            near = [
                x
                for x in marks
                if abs((parse_utc(x[0]) - t0).total_seconds()) <= 30
                and (
                    x[1].startswith("pc_")
                    or x[2].startswith("status_0d")
                    or x[2].startswith("mrt_risk")
                )
            ]
            print(f"  nearby ({len(near)}):")
            for x in near:
                print(f"    {x[0]}  {x[1]}  game={x[5]}")

            # Witness copper same window for tid 37BC
            print("  witness pos41 packet_complete tid=37BC +/-30s:")
            w = []
            for e in iter_events(41):
                u = e.get("utc")
                if not u or e.get("ev") != "packet_complete":
                    continue
                if e.get("tid") != TID:
                    continue
                try:
                    if abs((parse_utc(u) - t0).total_seconds()) > 30:
                        continue
                except Exception:
                    continue
                w.append((u, opcode(e), e.get("seq"), e["_file"]))
            w.sort()
            for row in w:
                print(f"    {row[0]}  op={row[1]} seq={row[2]}  file={row[3]}")

    # Perf: synth done counts last 90 min vs whole
    synth = [m for m in marks if m[2] == "status_0d_synth_done"]
    synth90 = [m for m in synth if parse_utc(m[0]) >= start_win]
    print(f"\n=== performance snapshot ===")
    print(f"status_0d_synth_done total={len(synth)} last90m={len(synth90)}")
    print(f"mrt_risk_hand_txn_restart total={sum(1 for m in marks if m[2]=='mrt_risk_hand_txn_restart')}")
    # opcode mix last 90m
    opmix = Counter(m[3] for m in last90 if m[1].startswith("pc_"))
    print("pc opcode mix last90m:", dict(opmix.most_common(20)))


if __name__ == "__main__":
    main()
