"""Per-kind rotation drills (SPEC-P5 §2, §6) — the AC-is-executed proof.

One representative of **every** kind runs ``rotate --apply`` end-to-end against in-proc
fakes with a SIM value (never a live credential): the staged lifecycle completes, each
stage gated on the prior; ``retire-old`` is refused until consumers are verified; and
the sooth canary runs where the entry is a cross-family (org-secret) surface. A kind
whose drill never ran is a kind whose rotation fails when needed — the drill is the proof.
"""

from __future__ import annotations

from datetime import UTC, datetime

import pytest

from mgf.secrets import rotate as rot
from mgf.secrets import store as store_mod
from mgf.secrets.context import Context
from mgf.secrets.hosts import HostStat
from mgf.secrets.model import KINDS
from mgf.secrets.sync import CanaryResult
from tests._fakes import FakeForge, FakeHost, FakeWoodpecker
from tests._store import make_store_env

FIXED = datetime(2026, 7, 22, 12, 0, 0, tzinfo=UTC)
SIM_VALUE = b"SIM_ROTATION_VALUE_do_not_leak_5a7e"
SIM_KEYPAIR = b"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5SIMpub drill-key\n-----SIM PRIVATE-----\n"


class _RecordingCanary:
    def __init__(self, ok: bool = True) -> None:
        self.calls = 0
        self._ok = ok

    def __call__(self) -> CanaryResult:
        self.calls += 1
        return CanaryResult(self._ok, "sooth canary (drill) " + ("green" if self._ok else "RED"))


def _plant(env, rel: str) -> None:
    p = env.fed_root / "mgf" / "my_stuff" / rel
    p.parent.mkdir(parents=True, exist_ok=True)
    p.write_text("OLD_credential_present", encoding="utf-8")
    p.chmod(0o600)


def _rotator(env, ctx, canary=None):
    return rot.Rotator(
        env.registry,
        fed_root=env.fed_root,
        store_root=env.store_root,
        identity_path=env.operator_key,
        ctx=ctx,
        canary=canary,
        now=lambda: FIXED,
    )


# --------------------------------------------------------------------------- #
# Per-kind drill registry fragments (one representative each).
# --------------------------------------------------------------------------- #

_ENTRIES: dict[str, str] = {
    "ssh-deploy-key": """\
[drill_clone_key]
family = "mgf"
kind = "ssh-deploy-key"
purpose = "drill: read-only clone deploy key (org secret + forge key)"
consumers = ["clone step"]
recovery = "mint + staged rotate"
state = "active"
policy = { allowed_repos = "*", blast_radius = "read 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" },
  { type = "store" },
]
""",
    "api-token": """\
[drill_api_token]
family = "mgf"
kind = "api-token"
purpose = "drill: forge api token (local cache + store)"
consumers = ["forge REST"]
recovery = "re-mint on forge"
state = "active"
policy = { allowed_repos = "*", blast_radius = "forge API" }
rotation = { class = "token", staged = true }
verify = { probe = "forge-auth" }
surfaces = [
  { type = "local-file", repo = "mgf", path = "tokens/drill_api_token.txt", mode = "0600" },
  { type = "store" },
]
""",
    "password": """\
[drill_devpi_password]
family = "mgf"
kind = "password"
purpose = "drill: devpi password (org secret + local cache) — cross-family surface"
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/drill_devpi_password.txt", mode = "0600" },
  { type = "store" },
]
""",
    "jwt-minted": """\
[drill_reconciler_token]
family = "mgf"
kind = "jwt-minted"
purpose = "drill: minted-on-demand woodpecker token (elimination path)"
consumers = ["agent-reconciler.py"]
recovery = "mint per MGF_INFRA §2 (no stored token)"
state = "active"
policy = { allowed_repos = "*", blast_radius = "woodpecker admin" }
rotation = { class = "jwt", staged = false }
verify = { probe = "woodpecker-user" }
surfaces = [ { type = "local-file", repo = "mgf", path = "tokens/drill_reconciler.txt", mode = "0600" } ]
""",
    "openstack-appcred": """\
[drill_appcred]
family = "mgf"
kind = "openstack-appcred"
purpose = "drill: openstack application credential (clouds.yaml)"
consumers = ["mgf-cloud vm ops"]
recovery = "re-issue appcred on the OpenStack dashboard"
state = "active"
policy = { allowed_repos = "*", blast_radius = "manage VMs" }
rotation = { class = "appcred", staged = true }
verify = { probe = "openstack-token" }
surfaces = [
  { type = "local-file", repo = "mgf", path = "infomaniak/drill-clouds.yaml", mode = "0600" },
  { type = "store" },
]
""",
    "service-account": """\
[drill_heartbeat_sa]
family = "mgf"
kind = "service-account"
purpose = "drill: OpenObserve heartbeat service account (host-file + store)"
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 = "h1", path = "/etc/mgf/hb.token", mode = "0600", owner = "root" },
  { type = "store" },
]
""",
    "ssh-keypair": """\
[drill_push_key]
family = "mgf"
kind = "ssh-keypair"
purpose = "drill: codeberg push SSH keypair (local cache + store)"
consumers = ["git push"]
recovery = "re-add key on forge"
state = "active"
policy = { allowed_repos = "*", blast_radius = "push owned repos" }
rotation = { class = "static-key", staged = true }
verify = { probe = "forge-key-fingerprint" }
surfaces = [
  { type = "local-file", repo = "mgf", path = "ssh_key/drill_push", mode = "0600" },
  { type = "store" },
]
""",
    "oauth-app": """\
[drill_oauth_app]
family = "mgf"
kind = "oauth-app"
purpose = "drill: woodpecker OAuth app secret (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 = "h1", path = "/opt/woodpecker/.env", mode = "0600", owner = "root" },
  { type = "store" },
]
""",
}


def test_drills_cover_every_kind() -> None:
    assert set(_ENTRIES) == set(KINDS), (
        f"a kind has no rotation drill: {sorted(set(KINDS) - set(_ENTRIES))}"
    )


def _ctx_for(env, kind: str) -> Context:
    """Build a fake surface context appropriate to the kind's declared surfaces, and
    plant the OLD credential so probe-new sees a live credential during overlap."""
    wp = FakeWoodpecker(org_secrets={}, repos=[(1, "mgf-standard")])
    forge = FakeForge({"mgf-standard": []})
    hosts = FakeHost(
        {
            ("h1", "/etc/mgf/hb.token"): HostStat("present", mode="600", owner="root"),
            ("h1", "/opt/woodpecker/.env"): HostStat("present", mode="600", owner="root"),
        }
    )
    # plant old local files where the kind uses a local cache
    for rel in (
        "tokens/drill_api_token.txt",
        "tokens/drill_devpi_password.txt",
        "infomaniak/drill-clouds.yaml",
        "ssh_key/drill_push",
    ):
        _plant(env, rel)
    return Context(fed_root=env.fed_root, woodpecker=wp, forge=forge, hosts=hosts)


_IDS = {
    "ssh-deploy-key": "drill_clone_key",
    "api-token": "drill_api_token",
    "password": "drill_devpi_password",
    "jwt-minted": "drill_reconciler_token",
    "openstack-appcred": "drill_appcred",
    "service-account": "drill_heartbeat_sa",
    "ssh-keypair": "drill_push_key",
    "oauth-app": "drill_oauth_app",
}
_ORG_SURFACE_KINDS = {"ssh-deploy-key", "password"}  # carry a cross-family org secret


@pytest.mark.parametrize("kind", sorted(_ENTRIES))
def test_drill_completes_end_to_end(kind, tmp_path) -> None:
    env = make_store_env(tmp_path, extra=_ENTRIES[kind])
    ctx = _ctx_for(env, kind)
    canary = _RecordingCanary(ok=True)
    entry_id = _IDS[kind]
    value = SIM_KEYPAIR if kind in ("ssh-deploy-key", "ssh-keypair") else SIM_VALUE
    result = _rotator(env, ctx, canary).apply(entry_id, new_value=value, allow_retire=True)

    assert result.completed, [(s.name, s.status, s.message) for s in result.stages]
    # the sim value never reaches the result payload (AG-18).
    import json

    assert value.decode("utf-8", "surrogateescape") not in json.dumps(result.to_json())

    if kind == "jwt-minted":
        # elimination: re-mint path, no stored token, no retire-old of a stored value.
        assert result.elimination
        assert [s.name for s in result.stages] == list(rot.ELIMINATION_STAGES)
        assert not store_mod.cipher_path(env.store_root, entry_id).is_file()
        return

    assert [s.name for s in result.stages] == list(rot.STAGES)
    assert result.retired_old is True
    assert result.rotation_last == "2026-07-22"
    # the new value was encrypted into the store (register-alongside).
    assert store_mod.cipher_path(env.store_root, entry_id).is_file()
    # the canary ran iff the entry is a cross-family (org-secret) surface.
    if kind in _ORG_SURFACE_KINDS:
        assert canary.calls == 1, "a cross-family surface rotation MUST run the sooth canary"
    else:
        assert canary.calls == 0


@pytest.mark.parametrize("kind", ["api-token", "password", "openstack-appcred"])
def test_drill_retire_old_refused_until_consumers_verified(kind, tmp_path) -> None:
    """retire-old is gated: without --allow-retire it refuses, and the rotation does not
    complete — but every earlier stage (incl. verify-consumers) ran green first."""
    env = make_store_env(tmp_path, extra=_ENTRIES[kind])
    ctx = _ctx_for(env, kind)
    result = _rotator(env, ctx, _RecordingCanary()).apply(
        _IDS[kind], new_value=SIM_VALUE, allow_retire=False
    )
    assert not result.completed
    verify = [s for s in result.stages if s.name == rot.VERIFY_CONSUMERS]
    assert verify and verify[0].status == rot.STATUS_OK  # consumers verified…
    retire = result.stages[-1]
    assert retire.name == rot.RETIRE_OLD and retire.status == rot.STATUS_REFUSED  # …but old kept
    assert result.retired_old is False


def test_drill_red_canary_blocks_retire_old(tmp_path) -> None:
    """A RED sooth canary fails verify-consumers, so retire-old is never reached — the
    old credential is never retired against an unverified rotation (SPEC §1)."""
    env = make_store_env(tmp_path, extra=_ENTRIES["password"])
    ctx = _ctx_for(env, "password")
    result = _rotator(env, ctx, _RecordingCanary(ok=False)).apply(
        "drill_devpi_password", new_value=SIM_VALUE, allow_retire=True
    )
    assert not result.completed
    names = [s.name for s in result.stages]
    assert names[-1] == rot.VERIFY_CONSUMERS and result.stages[-1].status == rot.STATUS_FAILED
    assert rot.RETIRE_OLD not in names
    assert result.retired_old is False
