"""TC-08: full tick with the orchestrator down (integration).

With ``/metrics`` failing, the tick must not crash, must still collect host /
containers / deps, must produce EXACTLY ONE ``orchestrator_down`` alert (after
the debounce), suppress within cooldown, and emit recovery on restoration.
"""
from watchdog.collectors import orch as orch_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 _cfg(**kw):
    base = {
        "WATCHDOG_TG_BOT_TOKEN": "t",
        "WATCHDOG_TG_CHAT_ID": "c",
        "WATCHDOG_ORCH_DOWN_TICKS": "2",
        "WATCHDOG_COOLDOWN_S": "1000",
        "WATCHDOG_CONTAINERS": "orchestrator",
    }
    return Config.from_env({**base, **kw})


def _clock():
    t = {"v": 0.0}

    def now():
        return t["v"]

    return t, now


def _down(monkeypatch):
    monkeypatch.setattr(
        orch_mod, "fetch_metrics",
        lambda *a, **k: orch_mod.FetchResult(ok=False, error="timeout"),
    )


def _up(monkeypatch):
    env = {"schema_version": 1, "generated_at": "2026-06-10T00: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 _orch_down_alerts(notifier):
    return [m for m in notifier.sent if "не отвечает" in m]


def test_tick_orch_down_one_alert_then_throttle_then_recovery(monkeypatch):
    notifier = _Notifier()
    t, now = _clock()
    dog = Watchdog(_cfg(), notifier=notifier, docker=_StubDocker(), now_provider=now)

    _down(monkeypatch)
    # tick 1: first failure -> debounced, NOT yet active -> no alert.
    dog.tick()
    assert _orch_down_alerts(notifier) == []

    # tick 2: second consecutive failure -> active -> EXACTLY ONE alert.
    t["v"] = 30.0
    dog.tick()
    assert len(_orch_down_alerts(notifier)) == 1

    # tick 3: still down, within cooldown -> throttled (no new alert).
    t["v"] = 60.0
    dog.tick()
    assert len(_orch_down_alerts(notifier)) == 1

    # restore: orchestrator answers again -> recovery message.
    _up(monkeypatch)
    t["v"] = 90.0
    dog.tick()
    recoveries = [m for m in notifier.sent if "восстановление" in m and "Орк" in m]
    assert len(recoveries) == 1


def test_tick_does_not_crash_when_everything_breaks(monkeypatch):
    # orch down + docker raising + no deps: tick still completes.
    class _BoomDocker:
        def inspect(self, name):
            raise RuntimeError("boom")

    notifier = _Notifier()
    dog = Watchdog(_cfg(), notifier=notifier, docker=_BoomDocker())
    _down(monkeypatch)
    dog.tick()  # must not raise
    dog.tick()
    assert len(_orch_down_alerts(notifier)) == 1
