"""Contract: broker self-restart detection (Tier-1 #8, R-T1-8 residual a).

A marker file beside the socket records that an instance is running. A cold start
(no prior marker — first boot, or a host reboot that cleared tmpfs) does NOT
reconcile; a restart (a prior instance left the marker by crashing/being killed,
host still up) re-derives hardware truth via the strict auto-reconcile.

This drives the real broker binary twice on a fresh socket and asserts the
detection from its startup log, since the behaviour lives in `main()` (the marker
component itself is unit-pinned in `restart_marker.zig`).
"""

from __future__ import annotations

import shutil
import signal
import tempfile
import time
from pathlib import Path

import pytest

from tests import _broker


def test_cold_start_then_restart_is_detected() -> None:
    if not _broker.server_exists():
        pytest.skip(f"broker binary not built; run: {_broker.build_command()}")
    is_stale, reason = _broker.broker_is_stale()
    if is_stale:
        pytest.fail(reason, pytrace=False)

    # A short temp dir keeps the socket path under the 108-byte AF_UNIX limit.
    tmpdir = tempfile.mkdtemp(prefix="mgfhrb-restart-")
    sock = str(Path(tmpdir) / "b.sock")
    marker = Path(sock + ".restart")
    try:
        # First boot — no prior marker → COLD start (no reconcile). kill -9 leaves
        # the marker behind (no clean exit) — the crash signature. communicate()
        # reaps the process and closes the captured pipe. (spawn_broker merges
        # the broker's stderr into the stdout PIPE.)
        p1 = _broker.spawn_broker(sock)
        time.sleep(0.2)
        p1.send_signal(signal.SIGKILL)
        boot1 = p1.communicate()[0].decode()
        assert "cold start" in boot1
        assert "RESTART detected" not in boot1
        assert marker.exists(), "a crash must leave the marker behind"

        # Second boot on the SAME socket — the leftover marker → RESTART detected,
        # and the broker auto-reconciles (strict: only confident vfio-pci orphans,
        # so on a host with none it's a clean no-op — never disrupts live devices).
        p2 = _broker.spawn_broker(sock)
        time.sleep(0.2)
        p2.terminate()
        boot2 = p2.communicate()[0].decode()
        assert "RESTART detected" in boot2
        assert "orphan(s) quarantined" in boot2
    finally:
        shutil.rmtree(tmpdir, ignore_errors=True)


def test_clean_sigterm_stop_clears_marker_so_next_start_is_cold() -> None:
    """A deliberate operator stop (SIGTERM — systemd `stop`, `kill`, Ctrl-C)
    must remove the marker, so the NEXT start is a cold start, not a spurious
    crash-restart. The broker's accept loop never returns, so the marker is
    cleared by a signal handler, not a `defer`. Only an uncatchable SIGKILL /
    crash leaves the marker behind (the restart case, covered above)."""
    if not _broker.server_exists():
        pytest.skip(f"broker binary not built; run: {_broker.build_command()}")
    is_stale, reason = _broker.broker_is_stale()
    if is_stale:
        pytest.fail(reason, pytrace=False)

    tmpdir = tempfile.mkdtemp(prefix="mgfhrb-cleanstop-")
    sock = str(Path(tmpdir) / "b.sock")
    marker = Path(sock + ".restart")
    try:
        # First boot — COLD; the running instance holds the marker.
        p1 = _broker.spawn_broker(sock)
        time.sleep(0.2)
        assert marker.exists(), "a running instance holds the marker"
        # SIGTERM is a graceful stop: the handler unlinks the marker before exit.
        p1.terminate()
        boot1 = p1.communicate()[0].decode()
        assert "cold start" in boot1
        assert not marker.exists(), "a clean SIGTERM stop must clear the marker"

        # Second boot on the SAME socket — the marker is gone, so this is a COLD
        # start, NOT a spurious restart/auto-reconcile.
        p2 = _broker.spawn_broker(sock)
        time.sleep(0.2)
        p2.terminate()
        boot2 = p2.communicate()[0].decode()
        assert "cold start" in boot2
        assert "RESTART detected" not in boot2
    finally:
        shutil.rmtree(tmpdir, ignore_errors=True)
