"""CCL diagnostics: trace + pointer dump + deadlock (ADR-0023 D14).

Trace
-----
Set ``KERNBENCH_CCL_TRACE=1`` (or any truthy value) to enable per-event
logging of CCL send/recv to stdout. Off by default.

Pointer dump
------------
``pointer_dump(engine)`` returns a multi-line string showing every PE_IPCQ's
ring buffer state (my_head, my_tail, peer_head_cache, peer_tail_cache).
Useful for diagnosing hangs.

Deadlock
--------
``IpcqDeadlock`` is raised by the engine when SimPy's schedule empties
while a request is still pending — typical of unmatched send/recv pairs.
The exception message includes the pointer dump.
"""
from __future__ import annotations

import os
from typing import Any


class IpcqDeadlock(RuntimeError):
    """Raised when the simulation cannot make further progress while a
    CCL request is still pending (D14 F3)."""


# ── Trace toggle ─────────────────────────────────────────────────────


_TRACE_ENABLED: bool = False


def reload_trace_setting() -> None:
    """Re-read the ``KERNBENCH_CCL_TRACE`` env var."""
    global _TRACE_ENABLED
    val = os.environ.get("KERNBENCH_CCL_TRACE", "")
    _TRACE_ENABLED = val.strip().lower() in {"1", "true", "yes", "on"}


def trace_enabled() -> bool:
    return _TRACE_ENABLED


# Initialise once at import time
reload_trace_setting()


# ── Trace event functions ────────────────────────────────────────────


def log_send(
    t_ns: float,
    sender: str,
    direction: str,
    nbytes: int,
    sender_seq: int,
) -> None:
    if not _TRACE_ENABLED:
        return
    print(
        f"[ccl t={t_ns:.1f} send] {sender} dir={direction} nbytes={nbytes} seq={sender_seq}",
        flush=True,
    )


def log_recv(
    t_ns: float,
    receiver: str,
    direction: str,
    nbytes: int,
) -> None:
    if not _TRACE_ENABLED:
        return
    print(
        f"[ccl t={t_ns:.1f} recv] {receiver} dir={direction} nbytes={nbytes}",
        flush=True,
    )


def log_credit_return(
    t_ns: float,
    sender: str,
    direction: str,
    consumer_seq: int,
) -> None:
    if not _TRACE_ENABLED:
        return
    print(
        f"[ccl t={t_ns:.1f} credit] {sender} dir={direction} seq={consumer_seq}",
        flush=True,
    )


# ── Pointer dump ─────────────────────────────────────────────────────


def pointer_dump(engine: Any) -> str:
    """Return a multi-line string of every PE_IPCQ's pointer state."""
    lines: list[str] = []
    components = getattr(engine, "_components", {})
    for node_id in sorted(components):
        if not node_id.endswith(".pe_ipcq"):
            continue
        comp = components[node_id]
        qps = getattr(comp, "queue_pairs", {})
        if not qps:
            continue
        lines.append(node_id)
        for d in sorted(qps):
            qp = qps[d]
            peer = qp["peer"]
            lines.append(
                f"  {d}: peer=sip{peer.sip}.cube{peer.cube}.pe{peer.pe}  "
                f"my_head={qp['my_head']} my_tail={qp['my_tail']}  "
                f"peer_head_cache={qp['peer_head_cache']} "
                f"peer_tail_cache={qp['peer_tail_cache']}"
            )
    return "\n".join(lines)


def print_pointer_dump(engine: Any) -> None:
    """Convenience: print pointer_dump(engine) to stdout."""
    print(pointer_dump(engine), flush=True)
