"""No-checkout config source (SPEC-P4 §1, forge-raw) + error-status-0-steps watch (§3)
+ concurrent config-fetch + C9 recency window (SPEC-P4 cron-hardening).

The no-checkout consumed-check reads the RAW COMMITTED ``.woodpecker.yml`` from the
forge contents API — NOT Woodpecker's event-rendered per-pipeline config (which strips
``from_secret`` + event-filtered steps and would false-clean the check). The CONTRACT
is ``forge-raw == local``."""

from __future__ import annotations

import time

from mgf.secrets.doctor import CONFIG_SOURCE_FORGE_RAW, Doctor
from mgf.secrets.registry import load
from mgf.secrets.woodpecker import PipelineInfo
from tests._fakes import FakeForge, FakeWoodpecker, write_pipeline

# A secret scoped only for push; a pipeline references it on tag -> a C2 scope finding.
REG = """\
[api_key]
family = "mgf"
kind = "api-token"
purpose = "p"
consumers = ["c"]
recovery = "r"
state = "active"
policy = { allowed_repos = "*", blast_radius = "b" }
rotation = { class = "token", staged = true }
verify = { probe = "auth" }
surfaces = [ { type = "woodpecker-org-secret", events = ["push"] } ]
"""

PIPELINE_TAG_MISUSE = """\
when:
  - event: push
  - event: tag
steps:
  - name: publish
    when:
      event: tag
    environment:
      E:
        from_secret: api_key
    commands: [echo x]
"""


def _reg(tmp_path):
    p = tmp_path / "SECRETS.toml"
    p.write_text(REG, encoding="utf-8")
    return load([p])


def _forgeraw(reg, tmp_path, wp, forge):
    return Doctor(
        reg,
        fed_root=tmp_path,
        woodpecker=wp,
        forge=forge,
        config_source=CONFIG_SOURCE_FORGE_RAW,
    )


def test_forgeraw_finds_scope_drift_without_a_checkout(tmp_path) -> None:
    """The consumed-check (C2) works with ZERO local checkout when the committed config
    comes from the forge contents API — the agent2 mode."""
    reg = _reg(tmp_path)
    wp = FakeWoodpecker(org_secrets={"api_key": {"push"}}, repos=[(7, "mgf-thing")])
    forge = FakeForge(files={"mgf-thing": PIPELINE_TAG_MISUSE})
    report = _forgeraw(reg, tmp_path, wp, forge).run(["woodpecker"])
    c2 = [f for f in report.findings if f.check == "C2"]
    assert c2 and c2[0].evidence["repo"] == "mgf-thing"
    assert "tag" in "".join(str(f.evidence.get("bad_events")) for f in c2)


def test_local_source_needs_a_checkout_forgeraw_does_not(tmp_path) -> None:
    """Same drift: local mode skips (no checkout); forge-raw catches it from the forge."""
    reg = _reg(tmp_path)
    wp = FakeWoodpecker(org_secrets={"api_key": {"push"}}, repos=[(7, "mgf-thing")])
    # local mode: no committed file on disk -> skipped, no C2 finding.
    local = Doctor(reg, fed_root=tmp_path, woodpecker=wp).run(["woodpecker"])
    assert not [f for f in local.findings if f.check == "C2"]
    assert any("no local .woodpecker.yml" in s for s in local.skipped)


# The false-clean bug the court found: Woodpecker's EVENT-RENDERED config strips the
# deployment-scoped from_secret refs (CIR-33), so the no-checkout mode must read the RAW
# committed file and match `local` EXACTLY — never report 0 while real drift is live.
PARITY_REG = """\
[held_secret]
family = "mgf"
kind = "api-token"
purpose = "deployment secret held (CIR-33)"
consumers = ["deploy"]
recovery = "r"
state = "held"
policy = { allowed_repos = "mgf-website", blast_radius = "b" }
rotation = { class = "token", staged = true }
verify = { probe = "auth" }
surfaces = [ { type = "woodpecker-repo-secret", repo = "mgf-website", events = ["deployment"] } ]
"""

PARITY_CONFIG = """\
when:
  - event: push
  - event: deployment
steps:
  - name: deploy
    when:
      event: deployment
    environment:
      A:
        from_secret: held_secret
      B:
        from_secret: ghost_secret
    commands: [echo deploy]
"""


def test_forgeraw_parity_with_local_on_deployment_scoped_secrets(tmp_path) -> None:
    """The critical contract: forge-raw finds the SAME C2/C4 findings as local for a
    deployment-scoped pipeline — never 0 (the event-rendered false-clean bug)."""
    (tmp_path / "SECRETS.toml").write_text(PARITY_REG, encoding="utf-8")
    reg = load([tmp_path / "SECRETS.toml"])
    # local: the committed file on disk.
    write_pipeline(tmp_path, "mgf-website", PARITY_CONFIG)
    local = Doctor(
        reg, fed_root=tmp_path, woodpecker=FakeWoodpecker(repos=[(3, "mgf-website")])
    ).run(["woodpecker"])
    # forge-raw: the SAME committed text from the forge (no checkout read).
    forge = FakeForge(files={"mgf-website": PARITY_CONFIG})
    raw = _forgeraw(reg, tmp_path, FakeWoodpecker(repos=[(3, "mgf-website")]), forge).run(
        ["woodpecker"]
    )

    def key(r):
        return sorted((f.check, f.id) for f in r.findings)

    assert key(local) == key(raw)  # parity
    assert key(raw)  # NON-EMPTY — the deployment drift is found, not false-cleaned
    checks = {f.check for f in raw.findings}
    assert "C2" in checks and "C4" in checks  # undefined ghost_secret (C2) + held ref (C4)


def test_forgeraw_config_unreadable_is_unverifiable_not_clean(tmp_path) -> None:
    reg = _reg(tmp_path)
    wp = FakeWoodpecker(org_secrets={"api_key": {"push"}}, repos=[(7, "mgf-thing")])
    forge = FakeForge(rate_limited={"mgf-thing"})  # 429 on the config fetch
    report = _forgeraw(reg, tmp_path, wp, forge).run(["woodpecker"])
    assert report.exit_code() == 1
    assert any(u.check == "C2" and "mgf-thing" in u.target for u in report.unverifiable)
    assert not report.findings  # 429 is unverifiable, NOT silently clean


def test_forgeraw_no_committed_file_is_skipped_not_a_finding(tmp_path) -> None:
    reg = _reg(tmp_path)
    wp = FakeWoodpecker(org_secrets={"api_key": {"push"}}, repos=[(7, "mgf-thing")])
    forge = FakeForge(files={})  # repo_file -> None (404: no committed file)
    report = _forgeraw(reg, tmp_path, wp, forge).run(["woodpecker"])
    assert any("no committed .woodpecker.yml" in s for s in report.skipped)
    assert not [f for f in report.findings if f.check == "C2"]


def test_parallel_config_fetch_preserves_semantics_across_many_repos(tmp_path) -> None:
    """Concurrent forge config-fetch (cron-perf) over many repos: every readable config
    is parsed, an unreadable one is unverifiable (never dropped), regardless of workers."""
    reg = _reg(tmp_path)
    n = 25
    repos = [(i, f"repo-{i}") for i in range(n)]
    good = "when:\n  - event: push\nsteps:\n  - name: x\n    commands: [echo x]\n"
    files = {f"repo-{i}": good for i in range(n) if i != 13}  # repo-13 is unavailable
    wp = FakeWoodpecker(org_secrets={"api_key": {"push"}}, repos=repos)
    forge = FakeForge(files=files, rate_limited={"repo-13"})
    doc = Doctor(
        reg,
        fed_root=tmp_path,
        woodpecker=wp,
        forge=forge,
        config_source=CONFIG_SOURCE_FORGE_RAW,
        config_workers=8,
    )
    report = doc.run(["woodpecker"])
    assert len(doc._pipelines or {}) == n - 1  # 24 readable configs all consumed
    assert any(u.check == "C2" and "repo-13" in u.target for u in report.unverifiable)
    assert report.exit_code() == 1


# --------------------------------------------------------------------------- #
# C9 — error-status-0-steps watch (§3).
# --------------------------------------------------------------------------- #


def test_c9_flags_error_status_with_zero_steps(tmp_path) -> None:
    reg = _reg(tmp_path)
    now = int(time.time())
    wp = FakeWoodpecker(
        org_secrets={"api_key": {"push"}},
        repos=[(7, "mgf-thing")],
        pipelines={
            7: [
                PipelineInfo(number=42, status="error", created=now, step_count=0, finished=now),
                PipelineInfo(number=41, status="success", created=now, step_count=6),
            ]
        },
    )
    findings = Doctor(reg, fed_root=tmp_path, woodpecker=wp).c9_error_status_empty()
    assert len(findings) == 1
    f = findings[0]
    assert f.check == "C9" and f.evidence["pipeline"] == 42
    assert f.evidence["repo"] == "mgf-thing" and f.evidence["as_of"] == now


def test_c9_recency_window_flags_recent_not_history(tmp_path) -> None:
    """The cron-hardening fix: an OLD error-0-step (already-resolved history) is NOT
    re-filed every run; only a RECENT one pages."""
    reg = _reg(tmp_path)
    now = int(time.time())
    old = now - 8 * 86400  # 8 days ago -> outside the default 24h window
    wp = FakeWoodpecker(
        org_secrets={"api_key": {"push"}},
        repos=[(7, "mgf-thing")],
        pipelines={
            7: [
                PipelineInfo(number=100, status="error", created=now, step_count=0, finished=now),
                PipelineInfo(number=25, status="error", created=old, step_count=0, finished=old),
            ]
        },
    )
    findings = Doctor(reg, fed_root=tmp_path, woodpecker=wp).c9_error_status_empty()
    nums = {f.evidence["pipeline"] for f in findings}
    assert nums == {100}  # the historical #25 is NOT paged
    # A wide window sees BOTH (the archive is reachable when explicitly asked for).
    wide = Doctor(
        reg, fed_root=tmp_path, woodpecker=wp, error_window_hours=24 * 365
    ).c9_error_status_empty()
    assert {f.evidence["pipeline"] for f in wide} == {100, 25}


def test_c9_uses_finished_time_over_created(tmp_path) -> None:
    """A pipeline created long ago but that FINISHED recently is recent (terminal time)."""
    reg = _reg(tmp_path)
    now = int(time.time())
    wp = FakeWoodpecker(
        org_secrets={"api_key": {"push"}},
        repos=[(7, "mgf-thing")],
        pipelines={
            7: [
                PipelineInfo(
                    number=7, status="error", created=now - 10 * 86400, step_count=0, finished=now
                )
            ]
        },
    )
    findings = Doctor(reg, fed_root=tmp_path, woodpecker=wp).c9_error_status_empty()
    assert {f.evidence["pipeline"] for f in findings} == {7}


def test_c9_ignores_error_with_steps_and_failure_with_zero(tmp_path) -> None:
    reg = _reg(tmp_path)
    wp = FakeWoodpecker(
        org_secrets={"api_key": {"push"}},
        repos=[(7, "mgf-thing")],
        pipelines={
            7: [
                # errored but 3 steps ran -> a normal step failure, not the silent-empty shape.
                PipelineInfo(number=50, status="error", created=1, step_count=3),
                # failure with 0 steps -> a different class (CI already surfaces it).
                PipelineInfo(number=49, status="failure", created=1, step_count=0),
                PipelineInfo(number=48, status="success", created=1, step_count=5),
            ]
        },
    )
    assert Doctor(reg, fed_root=tmp_path, woodpecker=wp).c9_error_status_empty() == []


def test_c9_pipeline_scan_unavailable_is_unverifiable(tmp_path) -> None:
    reg = _reg(tmp_path)
    wp = FakeWoodpecker(
        org_secrets={"api_key": {"push"}},
        repos=[(7, "mgf-thing")],
        unavailable_pipelines={7},
    )
    doc = Doctor(reg, fed_root=tmp_path, woodpecker=wp)
    assert doc.c9_error_status_empty() == []
    assert any(u.check == "C9" and "mgf-thing" in u.target for u in doc.unverifiable)


def test_c9_runs_in_the_default_woodpecker_surface(tmp_path) -> None:
    """A live empty-injection error pipeline reds a normal doctor run (not just the
    dedicated method)."""
    reg = _reg(tmp_path)
    write_pipeline(
        tmp_path,
        "mgf-thing",
        "when:\n  - event: push\nsteps:\n  - name: x\n    commands: [echo x]\n",
    )
    now = int(time.time())
    wp = FakeWoodpecker(
        org_secrets={"api_key": {"push"}},
        repos=[(7, "mgf-thing")],
        pipelines={7: [PipelineInfo(number=99, status="error", created=now, step_count=0)]},
    )
    report = Doctor(reg, fed_root=tmp_path, woodpecker=wp).run(["woodpecker"])
    assert any(f.check == "C9" and f.evidence["pipeline"] == 99 for f in report.findings)
