"""
packet_log.py — per-session interleaved log of WebSocket frames + bot ACTIONS, for reverse
engineering. When a script runs, control.py opens a session log here; every SENT/RECV frame and
every bot action is appended with a timestamp, so we can later line up "what the bot did" against
"which frames the client sent/received" — even after the session ends.

One file per session in ./packet_logs/ , named  <YYYYMMDD_HHMMSS>_<mode>.txt.
Columns:  <wallclock HH:MM:SS.mmm>  <+seconds since session start>  <KIND>  <detail>
KIND is SENT / RECV (frames) or ACTION (what the bot is doing).

Frames are encrypted in the body, but direction + length + WS-opcode + header bytes + TIMING are
visible — enough to map an action to the frame(s) it triggers by correlation.

This module is NOT hot-reloaded (its open-file state must persist across grind_local reloads), so the
action() / frame() calls are cheap no-ops whenever no session log is open.
"""
import os
import time
import threading

LOG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "packet_logs")
_lock = threading.Lock()
_state = {"file": None, "t0": 0.0, "path": None, "frames": 0, "actions": 0}


def start(mode):
    """Open a fresh session log (closing any previous). Returns the path, or None on failure."""
    stop()
    try:
        os.makedirs(LOG_DIR, exist_ok=True)
        ts = time.strftime("%Y%m%d_%H%M%S")
        path = os.path.join(LOG_DIR, f"{ts}_{mode}.txt")
        f = open(path, "w", encoding="utf-8", buffering=1)   # line-buffered → survives a crash
        f.write(f"# session {ts}  mode={mode}\n")
        f.write("# <wallclock>  <+rel_s>  <KIND>  <detail>   (KIND = SENT/RECV frame, or ACTION)\n")
        with _lock:
            _state.update({"file": f, "t0": time.time(), "path": path, "frames": 0, "actions": 0})
        return path
    except Exception:
        return None


def stop():
    """Close the current session log (writes a small summary footer)."""
    with _lock:
        f = _state["file"]
        if f:
            try:
                f.write(f"# end  frames={_state['frames']}  actions={_state['actions']}  "
                        f"duration={time.time()-_state['t0']:.1f}s\n")
                f.close()
            except Exception:
                pass
        _state["file"] = None
        _state["path"] = None


def active():
    return _state["file"] is not None


def current_path():
    return _state["path"]


def _write(kind, detail, count_key):
    with _lock:
        f = _state["file"]
        if not f:
            return
        now = time.time()
        try:
            f.write(f"{time.strftime('%H:%M:%S')}.{int((now % 1)*1000):03d}  "
                    f"+{now - _state['t0']:9.3f}  {kind:6} {detail}\n")
            _state[count_key] += 1
        except Exception:
            pass


def frame(direction, opcode, length, hexstr):
    """Log a WebSocket frame. direction = 'recv' | 'send'."""
    if _state["file"] is None:
        return
    _write("RECV" if direction == "recv" else "SENT",
           f"op{opcode} len={length:<5} {hexstr}", "frames")


def action(detail):
    """Log a bot action (a click / keypress / move / etc.) the moment it happens."""
    if _state["file"] is None:
        return
    _write("ACTION", detail, "actions")
