"""``launchd_restart`` must never kickstart a stale service definition.

``launchctl kickstart -k`` re-runs whatever definition launchd already holds.
After an update that definition was generated by the OLD checkout, so the
update's final restart faithfully revived a stale service; when that stale job
was also wedged, ``kickstart -k`` hung until its 90s timeout and the update
reported ``partial`` with "Could not restart the gateway" — on an install
whose code, deps and builds had all succeeded (observed in an update
rehearsal on a real macOS host, Sep 2026).

``refresh_launchd_plist_if_needed()`` (the ``hermes gateway start`` /
``gateway install`` repair path) already knows how to rewrite a stale plist
and bootout/bootstrap it; the contract pinned here is that ``launchd_restart``
runs it BEFORE any kickstart, and that a refresh which could not re-register
the job routes into the bounded bootstrap path instead of the unbounded
kickstart.
"""

from __future__ import annotations

from types import SimpleNamespace

import pytest

import hermes_cli.gateway as gateway_cli


@pytest.fixture
def launchd_seam(monkeypatch, tmp_path):
    """Neutralize process-side effects; record every launchctl invocation."""
    calls = []
    plist_path = tmp_path / "ai.hermes.gateway.plist"
    plist_path.write_text("<plist>whatever</plist>", encoding="utf-8")

    monkeypatch.setattr(gateway_cli, "get_launchd_label", lambda: "ai.hermes.gateway")
    monkeypatch.setattr(gateway_cli, "_launchd_domain", lambda: "gui/501")
    monkeypatch.setattr(gateway_cli, "get_launchd_plist_path", lambda: plist_path)
    monkeypatch.setattr("gateway.status.get_running_pid", lambda *a, **k: None)
    monkeypatch.setattr(gateway_cli, "_request_gateway_self_restart", lambda pid: False)
    monkeypatch.setattr(
        gateway_cli, "_graceful_restart_via_sigusr1", lambda pid, timeout, **_: False
    )
    monkeypatch.setattr(
        gateway_cli,
        "_wait_for_launchd_service_pid",
        lambda label, old_pid, timeout=10.0, *, domain: False,
    )
    monkeypatch.setattr(
        gateway_cli,
        "_launchd_unsupported_marker_exists",
        lambda: False,
    )

    def fake_run(cmd, check=False, timeout=None, **kwargs):
        calls.append(cmd)
        if check and cmd[0] == "launchctl" and cmd[1] == "kickstart" and "-k" in cmd:
            # The wedged-launchctl shape: kickstart never returns.
            raise gateway_cli.subprocess.TimeoutExpired(cmd, 90)
        return SimpleNamespace(returncode=0, stdout="", stderr="")

    monkeypatch.setattr(gateway_cli.subprocess, "run", fake_run)
    return calls


def test_stale_plist_is_refreshed_before_any_kickstart(monkeypatch, launchd_seam):
    """The update-restart path must repair the definition, not revive a stale one."""
    refreshed = []
    monkeypatch.setattr(
        gateway_cli, "refresh_launchd_plist_if_needed", lambda: refreshed.append(1) or True
    )
    # Even when the kickstart later wedges (TimeoutExpired escapes to the
    # caller, exactly as on the broken host), the refresh must already have
    # run — that ordering is the contract.
    with pytest.raises(gateway_cli.subprocess.TimeoutExpired):
        gateway_cli.launchd_restart()

    assert refreshed == [1], "refresh must run before the restart decision"
    kickstarts = [c for c in launchd_seam if c[:2] == ["launchctl", "kickstart"]]
    assert kickstarts, "a current definition still needs the kickstart relaunch"


def test_failed_refresh_routes_to_bounded_bootstrap_not_the_90s_kickstart(monkeypatch, launchd_seam):
    """When the refresh couldn't re-register the job, kickstart would hang again.

    The bootstrap path (30s timeouts, loud failure) is the bounded revival.
    """
    monkeypatch.setattr(gateway_cli, "refresh_launchd_plist_if_needed", lambda: False)

    gateway_cli.launchd_restart()

    kickstarts = [c for c in launchd_seam if "kickstart" in c]
    assert not [c for c in kickstarts if "-k" in c], (
        "kickstart -k after a failed refresh is the exact 90s hang this guards against"
    )
    bootstraps = [c for c in launchd_seam if c[:2] == ["launchctl", "bootstrap"]]
    assert bootstraps, "the bounded bootstrap revival must run instead"
