"""The cron wrapper's routing (SPEC-P4 §1/§5): findings -> deduped pager, clean ->
heartbeat + issue close, PAGE=0 escape hatch, dead-man's-switch heartbeat.
"""

from __future__ import annotations

from mgf.secrets.doctor import DoctorReport, Finding, Unverifiable
from mgf.secrets.watch import DEDUPE_QUERY, run_watch


class FakePager:
    """Records report/resolve calls and models one standing deduped issue."""

    def __init__(self, *, existing: bool = False) -> None:
        self.reports: list[tuple[str, str, str]] = []
        self.resolves: list[str] = []
        self._open = existing

    def report(self, *, dedupe_query: str, title: str, body: str) -> str:
        self.reports.append((dedupe_query, title, body))
        action = "updated" if self._open else "filed"
        self._open = True
        return action

    def resolve(self, *, dedupe_query: str, comment: str) -> str:
        self.resolves.append(dedupe_query)
        if not self._open:
            return "none"
        self._open = False
        return "closed"


class FakeHeartbeat:
    def __init__(self) -> None:
        self.beats: list[str] = []

    def beat(self, job: str) -> bool:
        self.beats.append(job)
        return True


def _report(*, findings=(), unverifiable=(), surfaces=("woodpecker", "forge")) -> DoctorReport:
    return DoctorReport(
        surfaces=list(surfaces), findings=list(findings), unverifiable=list(unverifiable)
    )


def _finding() -> Finding:
    return Finding("api_key", "C2", "high", "references 'api_key' on ['tag'] but scope is ['push']")


def test_findings_file_the_deduped_issue_and_heartbeat_still_fires() -> None:
    pager, hb = FakePager(), FakeHeartbeat()
    outcome = run_watch(_report(findings=[_finding()]), pager=pager, heartbeat=hb)
    assert outcome.paged and outcome.pager_action == "filed"
    assert pager.reports and pager.reports[0][0] == DEDUPE_QUERY
    # heartbeat_on defaults to 'always' — a drift run still proves liveness (a drift is
    # NOT a dead cron), so the dead-man's-switch means exactly "the cron stopped".
    assert hb.beats == ["secret-doctor"]


def test_unverifiable_alone_also_pages() -> None:
    pager, hb = FakePager(), FakeHeartbeat()
    unv = [Unverifiable("C5", "forge-deploy-key:repo-x", "forge HTTP 429")]
    outcome = run_watch(_report(unverifiable=unv), pager=pager, heartbeat=hb)
    assert outcome.paged and pager.reports
    assert not outcome.clean


def test_clean_run_closes_the_standing_issue_and_heartbeats() -> None:
    pager, hb = FakePager(existing=True), FakeHeartbeat()
    outcome = run_watch(_report(), pager=pager, heartbeat=hb)
    assert outcome.clean and not outcome.paged
    assert outcome.pager_action == "closed"  # re-scope clears the page
    assert hb.beats == ["secret-doctor"]


def test_second_findings_run_updates_not_duplicates() -> None:
    pager, hb = FakePager(), FakeHeartbeat()
    run_watch(_report(findings=[_finding()]), pager=pager, heartbeat=hb)
    outcome = run_watch(_report(findings=[_finding()]), pager=pager, heartbeat=hb)
    assert outcome.pager_action == "updated"  # deduped: one standing issue
    assert len(pager.reports) == 2


def test_page_false_suppresses_the_pager_but_computes_summary() -> None:
    pager, hb = FakePager(), FakeHeartbeat()
    outcome = run_watch(_report(findings=[_finding()]), pager=pager, heartbeat=hb, page=False)
    assert not pager.reports and outcome.pager_action == "suppressed"
    assert "api_key" in outcome.body  # the summary was still built (fire-testing)
    assert hb.beats == ["secret-doctor"]  # heartbeat independent of paging


def test_heartbeat_on_clean_only_skips_beat_on_findings() -> None:
    hb = FakeHeartbeat()
    run_watch(_report(findings=[_finding()]), pager=FakePager(), heartbeat=hb, heartbeat_on="clean")
    assert hb.beats == []  # SPEC-literal mode: no heartbeat on a drift run
    run_watch(_report(), pager=FakePager(), heartbeat=hb, heartbeat_on="clean")
    assert hb.beats == ["secret-doctor"]


def test_missing_heartbeat_never_raises_dead_mans_switch_pages_instead() -> None:
    class DeadHeartbeat:
        def beat(self, job: str) -> bool:
            return False  # fail-open: POST failed, no exception

    outcome = run_watch(_report(), pager=None, heartbeat=DeadHeartbeat())
    assert outcome.heartbeat_sent is False  # a stale heartbeat -> the watcher pages
