"""Incident replays — every failure class is a permanent fixture (SPEC §7.3).

Each fixture is named for its incident and drives the doctor against in-proc fakes
(no network): CIR-02 re-scope → C1+C2 · CIR-33 held-referenced → C4 · CIR-34
missing deploy key → C5 · expired JWT → C6 · unregistered steward secret → C3 ·
doc-form file → C6. The registry fixture below covers every P1 kind, which the
kind-extension meta-test consumes as fixture coverage.
"""

from __future__ import annotations

import base64
import json
import time
from pathlib import Path

from mgf.secrets.doctor import Doctor
from mgf.secrets.forge import DeployKey
from mgf.secrets.registry import Registry, load
from tests._fakes import FakeForge, FakeWoodpecker, write_pipeline

#: Kinds the fixture registry exercises — read by the kind-extension meta-test.
KINDS_UNDER_TEST = {
    "ssh-deploy-key",
    "ssh-keypair",
    "api-token",
    "jwt-minted",
    "password",
    "oauth-app",
    "service-account",
    "openstack-appcred",
}

# One entry per P1 kind, plus a held website-deploy secret. Structurally complete.
REGISTRY = """\
[codeberg_ssh_clone_key]
family = "mgf"
kind = "ssh-deploy-key"
purpose = "read-only SSH clone for all CI pipelines"
consumers = ["clone step"]
recovery = "mint + staged rotate"
state = "active"
policy = { allowed_repos = "*", blast_radius = "read all private repos" }
rotation = { class = "static-key", staged = true }
verify = { probe = "forge-key-fingerprint" }
surfaces = [
  { type = "woodpecker-org-secret", events = ["push", "pull_request", "tag", "manual", "cron"] },
  { type = "forge-deploy-key", repos = "ALL_MANAGED", readonly = true, title = "ci-clone-2026-07-12" },
]

[codeberg_clone_token]
family = "mgf"
kind = "api-token"
purpose = "long-lived clone token pinned in base templates"
consumers = ["clone credential"]
recovery = "re-mint on forge"
state = "active"
policy = { allowed_repos = "*", blast_radius = "clone private repos" }
rotation = { class = "token", staged = true }
verify = { probe = "forge-auth" }
surfaces = [ { type = "woodpecker-org-secret", events = ["push", "pull_request", "tag", "manual", "cron"] } ]

[devpi_password]
family = "mgf"
kind = "password"
purpose = "devpi upload password (tag-only publish)"
consumers = ["publish step"]
recovery = "reset on devpi host"
state = "active"
policy = { allowed_repos = "*", blast_radius = "org-wide tag secret" }
rotation = { class = "password", staged = true }
verify = { probe = "devpi-login" }
surfaces = [
  { type = "woodpecker-org-secret", events = ["tag"] },
  { type = "local-file", repo = "mgf", path = "tokens/devpi_password.txt", mode = "0600" },
]

[codberge_pat]
family = "mgf"
kind = "api-token"
purpose = "Codeberg PAT set (doc-form file — leak class)"
consumers = ["forge REST"]
recovery = "re-mint on forge"
state = "active"
policy = { allowed_repos = "*", blast_radius = "forge API" }
rotation = { class = "token", staged = false }
verify = { probe = "forge-auth" }
surfaces = [ { type = "local-file", repo = "mgf", path = "tokens/codberge", mode = "0600" } ]

[woodpecker_token_local]
family = "mgf"
kind = "jwt-minted"
purpose = "expired duplicate local Woodpecker token (retiring)"
consumers = ["woodpecker_api.sh fallback"]
recovery = "mint per MGF_INFRA §2"
state = "retiring"
policy = { allowed_repos = "*", blast_radius = "woodpecker admin" }
rotation = { class = "jwt", staged = false }
verify = { probe = "woodpecker-user" }
surfaces = [ { type = "local-file", repo = "mgf", path = "tokens/woodpecker_token.txt", mode = "0600" } ]

[codeberg_push_key]
family = "mgf"
kind = "ssh-keypair"
purpose = "Codeberg push SSH key"
consumers = ["git push"]
recovery = "re-add key on forge"
state = "active"
policy = { allowed_repos = "*", blast_radius = "push to owned repos" }
rotation = { class = "static-key", staged = true }
verify = { probe = "forge-key-fingerprint" }
surfaces = [ { type = "local-file", repo = "mgf", path = "ssh_key/codeberg", mode = "0600" } ]

[woodpecker_oauth_app]
family = "mgf"
kind = "oauth-app"
purpose = "Woodpecker Forgejo OAuth app (client id/secret in server env)"
consumers = ["woodpecker server"]
recovery = "recreate OAuth app; re-enter env"
state = "active"
policy = { allowed_repos = "*", blast_radius = "CI login" }
rotation = { class = "oauth", staged = true }
verify = { probe = "server-env-present" }
surfaces = [ { type = "host-file", host = "195.15.241.48", path = "/opt/woodpecker/.env", mode = "0600", owner = "root" } ]

[heartbeat_sa]
family = "mgf"
kind = "service-account"
purpose = "OpenObserve heartbeat service account (least-privilege)"
consumers = ["backup heartbeats"]
recovery = "recreate OO SA; redistribute"
state = "active"
policy = { allowed_repos = "*", blast_radius = "OO ingest only" }
rotation = { class = "service-account", staged = true }
verify = { probe = "oo-sa" }
surfaces = [ { type = "host-file", host = "195.15.241.48", path = "/etc/mgf/backup-heartbeat.token", mode = "0600", owner = "root" } ]

[cost_watch_appcred]
family = "mgf"
kind = "openstack-appcred"
purpose = "OpenStack app credential for cost-watch"
consumers = ["mgf-cost-watch cron"]
recovery = "re-issue appcred on Infomaniak"
state = "active"
policy = { allowed_repos = "mgf-cost-watch", blast_radius = "read cloud usage" }
rotation = { class = "appcred", staged = true }
verify = { probe = "openstack-appcred" }
surfaces = [ { type = "local-file", repo = "mgf", path = "infomaniak/cost-watch-appcred-clouds.yaml", mode = "0600" } ]

[infomaniak_ssh_key]
family = "mgf"
kind = "ssh-keypair"
purpose = "website deploy SSH key — HELD pending founder go-live"
consumers = ["mgf-website deploy step"]
recovery = "held; arm only on founder word"
state = "held"
policy = { allowed_repos = "mgf-website", blast_radius = "publish website" }
rotation = { class = "static-key", staged = true }
verify = { probe = "forge-key-fingerprint" }
surfaces = [ { type = "woodpecker-repo-secret", repo = "mgf-website", events = ["deployment"] } ]
"""


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


def _make_jwt(days_from_now: int) -> str:
    def b64(obj: dict) -> str:
        return base64.urlsafe_b64encode(json.dumps(obj).encode()).rstrip(b"=").decode()

    header = b64({"alg": "HS256", "typ": "JWT"})
    payload = b64({"type": "user", "user-id": "1", "exp": int(time.time()) + days_from_now * 86400})
    return f"{header}.{payload}.sig"


# --------------------------------------------------------------------------- #


def test_fixture_registry_covers_all_kinds(tmp_path) -> None:
    reg = _reg(tmp_path)
    assert {e.kind for e in reg} == KINDS_UNDER_TEST


def test_cir02_rescope_reds_c1_and_c2(tmp_path) -> None:
    reg = _reg(tmp_path)
    write_pipeline(
        tmp_path,
        "mgf-cost-watch",
        """\
when:
  - event: cron
  - event: manual
clone:
  - name: clone
    settings:
      ssh-key-private:
        from_secret: codeberg_ssh_clone_key
steps:
  - name: cost
    commands: [echo hi]
""",
    )
    wp = FakeWoodpecker(
        # the 07-13 broken scope: clone key dropped cron/manual
        org_secrets={
            "codeberg_ssh_clone_key": {"push", "pull_request", "tag"},
            "codeberg_clone_token": {"push", "pull_request", "tag", "manual", "cron"},
            "devpi_password": {"tag"},
        },
        repos=[(25, "mgf-cost-watch")],
    )
    report = Doctor(reg, fed_root=tmp_path, woodpecker=wp).run(["woodpecker"])
    checks = {(f.check, f.id) for f in report.findings}
    assert ("C1", "codeberg_ssh_clone_key") in checks  # declared != live
    assert ("C2", "codeberg_ssh_clone_key") in checks  # cron/manual not covered
    assert report.exit_code() == 1


def test_cir33_held_referenced_reds_c4(tmp_path) -> None:
    reg = _reg(tmp_path)
    write_pipeline(
        tmp_path,
        "mgf-website",
        """\
when:
  - event: deployment
steps:
  - name: deploy
    environment:
      SSH_KEY:
        from_secret: infomaniak_ssh_key
    commands: [echo deploy]
""",
    )
    wp = FakeWoodpecker(
        org_secrets={
            "codeberg_ssh_clone_key": {"push", "pull_request", "tag", "manual", "cron"},
            "codeberg_clone_token": {"push", "pull_request", "tag", "manual", "cron"},
            "devpi_password": {"tag"},
        },
        repos=[(30, "mgf-website")],
    )
    report = Doctor(reg, fed_root=tmp_path, woodpecker=wp).run(["woodpecker"])
    c4 = [f for f in report.findings if f.check == "C4" and f.id == "infomaniak_ssh_key"]
    assert c4, "held website secret referenced on deployment must red C4"
    assert "HELD" in c4[0].finding


def test_cir34_missing_deploy_key_reds_c5(tmp_path) -> None:
    reg = _reg(tmp_path)
    key = DeployKey(title="ci-clone-2026-07-12", fingerprint="SHA256:abc", read_only=True)
    forge = FakeForge(
        {
            "mgf-standard": [key],
            "first-light": [key],
            "soothrede": [],  # CIR-34: rollout silently missed this repo
        }
    )
    wp = FakeWoodpecker(repos=[(1, "mgf-standard"), (2, "first-light"), (3, "soothrede")])
    report = Doctor(reg, fed_root=tmp_path, woodpecker=wp, forge=forge).run(["forge"])
    c5 = [f for f in report.findings if f.check == "C5" and f.evidence.get("repo") == "soothrede"]
    assert c5, "a repo missing the fleet deploy key must red C5 (CIR-34)"
    assert report.exit_code() == 1


def test_expired_jwt_reds_c6(tmp_path) -> None:
    reg = _reg(tmp_path)
    f = tmp_path / "mgf" / "my_stuff" / "tokens" / "woodpecker_token.txt"
    f.parent.mkdir(parents=True)
    f.write_text(_make_jwt(days_from_now=-3), encoding="utf-8")
    report = Doctor(reg, fed_root=tmp_path).run(["local"])
    c6 = [
        f_
        for f_ in report.findings
        if f_.check == "C6" and f_.id == "woodpecker_token_local" and "EXPIRED" in f_.finding
    ]
    assert c6, "an expired local JWT must red C6"
    assert c6[0].severity == "high"


def test_unregistered_steward_secret_reds_c3(tmp_path) -> None:
    reg = _reg(tmp_path)
    wp = FakeWoodpecker(
        org_secrets={
            "codeberg_ssh_clone_key": {"push", "pull_request", "tag", "manual", "cron"},
            "codeberg_clone_token": {"push", "pull_request", "tag", "manual", "cron"},
            "devpi_password": {"tag"},
            "steward_token": {"push"},  # appeared out of nowhere — nobody declared it
        },
        repos=[],
    )
    report = Doctor(reg, fed_root=tmp_path, woodpecker=wp).run(["woodpecker"])
    c3 = [
        f for f in report.findings if f.check == "C3" and f.evidence.get("name") == "steward_token"
    ]
    assert c3, "a live org secret absent from the registry must red C3"


def test_unregistered_steward_deploy_key_reds_c3(tmp_path) -> None:
    reg = _reg(tmp_path)
    forge = FakeForge(
        {
            "mgf-standard": [
                DeployKey(title="ci-clone-2026-07-12", fingerprint="SHA256:abc", read_only=True),
                DeployKey(
                    title="steward@woodpecker-prod", fingerprint="SHA256:zzz", read_only=True
                ),
            ],
        }
    )
    wp = FakeWoodpecker(repos=[(1, "mgf-standard")])
    report = Doctor(reg, fed_root=tmp_path, woodpecker=wp, forge=forge).run(["forge"])
    c3 = [
        f
        for f in report.findings
        if f.check == "C3" and f.evidence.get("title") == "steward@woodpecker-prod"
    ]
    assert c3, "an undeclared deploy key must red C3 (the steward-class detector)"


def test_doc_form_file_reds_c6(tmp_path) -> None:
    reg = _reg(tmp_path)
    f = tmp_path / "mgf" / "my_stuff" / "tokens" / "codberge"
    f.parent.mkdir(parents=True)
    # A doc-form token file: prose + 4 PATs across multiple lines (bare-value expected).
    f.write_text("Codeberg PATs\nline two\nline three\nline four\n", encoding="utf-8")
    report = Doctor(reg, fed_root=tmp_path).run(["local"])
    c6 = [
        f_
        for f_ in report.findings
        if f_.check == "C6" and f_.id == "codberge_pat" and "DOC-FORM" in f_.finding
    ]
    assert c6, "a multi-line bare-value file must red C6 (the cat-a-doc leak class)"
