"""Is the recording anchor actually doing its job — not merely running?

`recording_anchor.py` is the only record of where UTC zero sits inside OBS's recording, and nothing in
the picture can reconstruct it afterwards. A day recorded without it can be watched but not cut: no
per-race clips, no replay, no chapters. So it needs a watcher, and the watcher has to answer the
question the office learned to ask the hard way on 2026-08-20, when the data plane was deaf for three
hours while systemd reported `active`: **is the SERVICE working**, not is the process alive.

For this process those differ in a specific way. The sampler writes a line only while OBS is
recording; with OBS closed it logs "unreachable" once a minute and writes nothing, which is correct
and is how it spends most of a week. So "the file has not grown" is not a fault on its own — it is
only a fault **while OBS says it is recording**. That pairing is the whole of this module:

    OBS recording + file stale   -> `stale`    the sampler is wedged; restart it
    OBS recording + file fresh   -> `ok`
    OBS not recording            -> `idle`     nothing to write; not a fault
    OBS unreachable              -> `unknown`  we could not look; never `ok`, never a restart

`unknown` is deliberately not a restart. If OBS is down, restarting the sampler changes nothing and
would put it in a crash loop for the duration of a lunch break — and a watchdog that acts on a reading
it could not take is worse than one that waits.
"""
from __future__ import annotations

import argparse
import sys
import time
from pathlib import Path

_HERE = Path(__file__).resolve().parent
if str(_HERE) not in sys.path:
    sys.path.insert(0, str(_HERE))

from recording_anchor import read_samples  # noqa: E402

#: How old the newest sample may be before the sampler is not keeping up. The sampler ticks every 60 s,
#: so this is three ticks: long enough to ride out one slow round trip to OBS and a busy minute, short
#: enough that a wedge is caught inside a race rather than at the end of a day.
STALE_AFTER_S = 200.0

OK, STALE, IDLE, UNKNOWN = "ok", "stale", "idle", "unknown"


def verdict(samples: list[dict], *, now_ms: float, obs_recording: bool | None,
            stale_after_s: float = STALE_AFTER_S) -> tuple[str, str]:
    """`(verdict, why)` — pure, so the decision is testable without OBS or a clock.

    `obs_recording=None` means the question could not be asked.
    """
    newest = max((int(s.get("tUtcMs", 0)) for s in samples), default=0)
    age_s = (now_ms - newest) / 1000.0 if newest else None
    seen = f"newest sample {age_s:.0f}s old" if age_s is not None else "no samples yet"

    if obs_recording is None:
        return UNKNOWN, f"OBS could not be reached, so nothing can be concluded ({seen})"
    if not obs_recording:
        return IDLE, f"OBS is not recording, so there is nothing to write ({seen})"
    if age_s is None:
        return STALE, "OBS is recording and the anchor log has no samples at all"
    if age_s > stale_after_s:
        return STALE, f"OBS is recording but the anchor has not been written for {age_s:.0f}s"
    return OK, f"OBS is recording and the anchor is current ({seen})"


def obs_recording_now() -> bool | None:
    """Ask OBS whether it is recording. `None` when it cannot be asked at all.

    Imported from `director_obs` rather than `obs_mcp_server` for the same reason the sampler is: a
    supervised process on the render box must not need an agent framework installed to start.
    """
    try:
        from director_obs import obs_status  # noqa: PLC0415 — optional dep, only when running live

        return bool(obs_status().get("recording"))
    except Exception:                                    # noqa: BLE001 — OBS closed is a normal minute
        return None


def main(argv=None) -> int:                                       # noqa: ANN001
    p = argparse.ArgumentParser(description="Is the recording anchor keeping up?")
    p.add_argument("--anchor", required=True, type=Path, help="the recording_anchor.jsonl it writes")
    p.add_argument("--stale-after", type=float, default=STALE_AFTER_S)
    a = p.parse_args(argv)

    state, why = verdict(read_samples(a.anchor), now_ms=time.time() * 1000,
                         obs_recording=obs_recording_now(), stale_after_s=a.stale_after)
    print(f"[anchor-watchdog] {state}: {why}")
    # Only `stale` is actionable. The caller restarts on exit 1 and does nothing otherwise, so an
    # unreachable OBS never turns into a restart loop.
    return 1 if state == STALE else 0


if __name__ == "__main__":
    raise SystemExit(main())
