"""ORCH-111 TC-07: full tick -> dispatch of the proc_blocking alert (integration).

REGRESS: ``Watchdog.tick()`` with a collector that returns a long-lived blocking
process must dispatch exactly one ``proc_blocking`` alert through the fake
Notifier — even though ``/metrics`` reports no ``stuck`` stage and no hung agent.
With the kill-switch OFF the path is inert (byte-for-byte as before ORCH-111).

The orchestrator ``/metrics`` envelope is stubbed healthy so ONLY the new signal
can fire; the proc collector is stubbed at the module boundary so the real
``_collect_proc`` gate + wrapper still execute.
"""
from watchdog.collectors import orch as orch_mod
from watchdog.collectors import proc as proc_mod
from watchdog.config import Config
from watchdog.core import Watchdog


class _Notifier:
    def __init__(self):
        self.sent = []

    def send(self, text):
        self.sent.append(text)
        return True


class _StubDocker:
    def inspect(self, name):
        return {"State": {"Status": "running"}}


def _healthy_metrics(monkeypatch):
    env = {
        "schema_version": 1,
        "generated_at": "2026-06-15T00:00:00Z",
        "clk_tck": 100,
        "agents": [],
        "stages": [],
        "queue": {"depth": 0, "counts": {"failed": 0}},
    }
    monkeypatch.setattr(
        orch_mod, "fetch_metrics",
        lambda *a, **k: orch_mod.FetchResult(ok=True, envelope=env),
    )


def _cfg(**kw):
    base = {
        "WATCHDOG_TG_BOT_TOKEN": "t",
        "WATCHDOG_TG_CHAT_ID": "c",
        "WATCHDOG_PROC_ENABLED": "true",
        "WATCHDOG_PROC_AGE_MIN": "60",  # proc_age_s == 3600
        "WATCHDOG_CONTAINERS": "orchestrator",
    }
    return Config.from_env({**base, **kw})


def _blocking(monkeypatch, age_s=7200.0):
    rec = {"pid": 4242, "cmdline": "python3 -m pytest tests/test_install_lite_script.py",
           "age_s": age_s, "cpu_s": 99999.0, "start_ticks": 1}
    monkeypatch.setattr(proc_mod, "collect_candidates", lambda *a, **k: [rec])


def _proc_alerts(notifier):
    return [m for m in notifier.sent if "Блокирующий процесс" in m]


def test_tc07_tick_dispatches_proc_blocking_alert(monkeypatch):
    _healthy_metrics(monkeypatch)
    _blocking(monkeypatch)
    notifier = _Notifier()
    dog = Watchdog(_cfg(), notifier=notifier, docker=_StubDocker(), now_provider=lambda: 0.0)

    dog.tick()

    alerts = _proc_alerts(notifier)
    assert len(alerts) == 1
    assert "4242" in alerts[0]
    assert "pytest" in alerts[0]
    assert alerts[0].startswith("\U0001f534")  # red ALERT prefix


def test_tc07_killswitch_off_dispatches_nothing(monkeypatch):
    _healthy_metrics(monkeypatch)
    # Even if the collector WOULD return a blocking process, the gate skips it.
    called = {"n": 0}

    def _boom(*a, **k):
        called["n"] += 1
        return [{"pid": 1, "cmdline": "pytest", "age_s": 9e9, "cpu_s": 0.0}]

    monkeypatch.setattr(proc_mod, "collect_candidates", _boom)
    notifier = _Notifier()
    dog = Watchdog(
        _cfg(WATCHDOG_PROC_ENABLED="false"),
        notifier=notifier, docker=_StubDocker(), now_provider=lambda: 0.0,
    )

    dog.tick()

    assert _proc_alerts(notifier) == []
    assert called["n"] == 0  # collector never invoked when disabled (zero overhead)


def test_tc07_in_budget_process_does_not_alert(monkeypatch):
    # A process below the threshold (legitimate in-flight run) -> no alert (AC-4).
    _healthy_metrics(monkeypatch)
    _blocking(monkeypatch, age_s=600.0)
    notifier = _Notifier()
    dog = Watchdog(_cfg(), notifier=notifier, docker=_StubDocker(), now_provider=lambda: 0.0)

    dog.tick()

    assert _proc_alerts(notifier) == []


def test_tc07_tick_never_raises_when_collector_explodes(monkeypatch):
    _healthy_metrics(monkeypatch)

    def _explode(*a, **k):
        raise RuntimeError("boom")

    monkeypatch.setattr(proc_mod, "collect_candidates", _explode)
    notifier = _Notifier()
    dog = Watchdog(_cfg(), notifier=notifier, docker=_StubDocker(), now_provider=lambda: 0.0)

    dog.tick()  # must not raise — collector error degrades to one skipped signal
    assert _proc_alerts(notifier) == []
