"""The deploy ledger — closing sync's blind spot on a write-only value surface.

Woodpecker's API returns a secret's name, events and images, never its value. So
``sync`` could only ever compare METADATA, while its empty plan announced
"declared == deployed". The gap has a name and a cost: the store held the
corrected ``codeberg_repo_read`` (a stripped trailing newline) while Woodpecker
still served the old one; every ``sync`` reported clean, and mgf-hrb's pipeline
failed with an opaque ProtocolError for a session before anyone found it.

These tests fix the shape of the answer:

* a store rewritten since the last push plans a ``refresh-value`` (the mgf-hrb case);
* a secret with no push on record is ``unverifiable`` — stated, never silent;
* neither state breaks ``plan → apply → plan`` idempotence (SPEC §2).
"""

from __future__ import annotations

from datetime import UTC, datetime
from pathlib import Path

from pyrage import x25519

from mgf.secrets import store as store_mod
from mgf.secrets.registry import load
from mgf.secrets.sync import Reconciler
from tests._fakes import FakeWoodpecker

REGISTRY = """\
[org_tok]
family = "mgf"
kind = "api-token"
purpose = "org secret with a stored value — the deploy-ledger subject"
consumers = ["clone"]
recovery = "re-mint"
state = "active"
policy = { allowed_repos = "*", blast_radius = "low" }
rotation = { class = "token", staged = true }
verify = { probe = "forge-auth" }
surfaces = [ { type = "woodpecker-org-secret", events = ["push"] }, { type = "store" } ]
"""

#: A jwt-minted-shaped entry: a Woodpecker surface with NO store surface, so there
#: is no stored value that could ever be stale. Kept in its own registry — in the
#: shared one it has no value to push and every plan wants to create it.
MINTED_REGISTRY = (
    REGISTRY
    + """
[minted]
family = "mgf"
kind = "api-token"
purpose = "jwt-minted: no stored value, so nothing can be stale"
consumers = ["ci"]
recovery = "re-mint on the host"
state = "active"
policy = { allowed_repos = "*", blast_radius = "low" }
rotation = { class = "token", staged = false }
verify = { probe = "forge-auth" }
surfaces = [ { type = "woodpecker-org-secret", events = ["push"] } ]
"""
)


def _env(tmp_path: Path):
    fed = tmp_path / "fed"
    (fed / "mgf-standard").mkdir(parents=True)
    store = tmp_path / "store"
    (store / "secrets").mkdir(parents=True)
    founder, operator = x25519.Identity.generate(), x25519.Identity.generate()
    okey = tmp_path / "operator.key"
    okey.write_text(str(operator) + "\n", encoding="utf-8")
    okey.chmod(0o600)
    (store / "recipients.txt").write_text(
        f"founder  {founder.to_public()}\noperator {operator.to_public()}\n", encoding="utf-8"
    )
    (fed / "mgf-standard" / "SECRETS.toml").write_text(REGISTRY, encoding="utf-8")
    _put(store, "org_tok", b"the-original-value")
    return fed, store, okey


def _put(store: Path, entry_id: str, value: bytes) -> None:
    store_mod.store_put(
        entry_id,
        value,
        root=store,
        encrypted_at=datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
        family="mgf",
        kind="api-token",
    )


def _rec(fed, store, okey, **clients):
    reg = load([fed / "mgf-standard" / "SECRETS.toml"])
    return Reconciler(reg, fed_root=fed, store_root=store, identity_path=str(okey), **clients)


# --------------------------------------------------------------------------- #
# The mgf-hrb case: the store moved, the surface did not.
# --------------------------------------------------------------------------- #


def test_a_rotated_store_value_is_detected_as_stale(tmp_path) -> None:
    """THE regression. Before the ledger, this plan was empty and sync said
    'declared == deployed' while the surface served a value nobody had updated."""
    fed, store, okey = _env(tmp_path)
    wp = FakeWoodpecker(org_secrets={}, repos=[])
    rec = _rec(fed, store, okey, woodpecker=wp)
    rec.apply(["woodpecker"], skip_canary=True)  # create + record the deployment
    assert rec.plan(["woodpecker"]).is_empty  # settled

    _put(store, "org_tok", b"the-ROTATED-value")  # a rotate, no re-push

    plan = rec.plan(["woodpecker"])
    assert not plan.is_empty, "a store rewritten since the last push MUST plan a refresh"
    assert [(a.op, a.id) for a in plan.actions] == [("refresh-value", "org_tok")]
    assert "store rewritten" in plan.actions[0].detail["reason"]


def test_the_refresh_actually_changes_what_the_surface_serves(tmp_path) -> None:
    # Planning the action is not the point — the deployed value must change. This
    # asserts against the fake's recorded value, which the real API never exposes.
    fed, store, okey = _env(tmp_path)
    wp = FakeWoodpecker(org_secrets={}, repos=[])
    rec = _rec(fed, store, okey, woodpecker=wp)
    rec.apply(["woodpecker"], skip_canary=True)
    assert wp.deployed_value("org_tok") == b"the-original-value"

    _put(store, "org_tok", b"the-ROTATED-value")
    rec.apply(["woodpecker"], skip_canary=True)
    assert wp.deployed_value("org_tok") == b"the-ROTATED-value"


def test_plan_apply_plan_is_idempotent_after_a_refresh(tmp_path) -> None:
    # SPEC §2. A refresh that did not re-record the deployment would replan forever.
    fed, store, okey = _env(tmp_path)
    wp = FakeWoodpecker(org_secrets={}, repos=[])
    rec = _rec(fed, store, okey, woodpecker=wp)
    rec.apply(["woodpecker"], skip_canary=True)
    _put(store, "org_tok", b"the-ROTATED-value")
    rec.apply(["woodpecker"], skip_canary=True)
    assert rec.plan(["woodpecker"]).is_empty


def test_an_unchanged_store_plans_nothing(tmp_path) -> None:
    # The false-positive control: without it, a check that fired on everything
    # would still satisfy the staleness test above.
    fed, store, okey = _env(tmp_path)
    wp = FakeWoodpecker(org_secrets={}, repos=[])
    rec = _rec(fed, store, okey, woodpecker=wp)
    rec.apply(["woodpecker"], skip_canary=True)
    assert rec.plan(["woodpecker"]).is_empty
    assert rec.plan(["woodpecker"]).is_empty  # and stays empty on re-plan


# --------------------------------------------------------------------------- #
# Honesty: what is NOT checked must be said.
# --------------------------------------------------------------------------- #


def test_a_secret_with_no_push_on_record_is_unverifiable_not_silent(tmp_path) -> None:
    """A secret that already exists on the surface, deployed before the ledger
    existed (or by hand). Sync cannot compare its value to anything, and must
    say so rather than let an empty plan imply it checked."""
    fed, store, okey = _env(tmp_path)
    wp = FakeWoodpecker(org_secrets={"org_tok": {"push"}}, repos=[])
    plan = _rec(fed, store, okey, woodpecker=wp).plan(["woodpecker"])
    assert plan.is_empty  # nothing actionable...
    note = next(n for n in plan.notes if n.kind == "unverifiable")  # ...but NOT silent
    assert note.id == "org_tok"
    assert "never returns a secret value" in note.message
    assert "no push is on record" in note.message


def test_unverifiable_is_a_note_and_never_an_action(tmp_path) -> None:
    # An unverifiable that became an action would make `plan → apply → plan`
    # non-empty forever, breaking the idempotence property it must not touch.
    fed, store, okey = _env(tmp_path)
    wp = FakeWoodpecker(org_secrets={"org_tok": {"push"}}, repos=[])
    rec = _rec(fed, store, okey, woodpecker=wp)
    assert rec.plan(["woodpecker"]).is_empty
    rec.apply(["woodpecker"], skip_canary=True)
    assert rec.plan(["woodpecker"]).is_empty


def test_a_rescope_apply_does_NOT_establish_a_record(tmp_path) -> None:
    """Written first as the opposite assertion, and it failed — correctly. A re-scope
    pushes no value, so recording a deployment would CLAIM the deployed value matches
    today's store when nothing established that. The honest outcome is: still
    unverifiable. This test exists to keep that from being "fixed" into a lie."""
    fed, store, okey = _env(tmp_path)
    wp = FakeWoodpecker(org_secrets={"org_tok": {"pull_request"}}, repos=[])  # wrong scope
    rec = _rec(fed, store, okey, woodpecker=wp)
    assert [a.op for a in rec.plan(["woodpecker"]).actions] == ["update-secret"]
    rec.apply(["woodpecker"], skip_canary=True)
    assert any(n.kind == "unverifiable" for n in rec.plan(["woodpecker"]).notes)


def test_adopt_values_establishes_a_baseline_for_an_existing_secret(tmp_path) -> None:
    """`--adopt-values` is the path the note points at. Without it the entire live
    fleet — every secret deployed before the ledger existed — would stay unverifiable
    until its next rotation, which is a feature that helps only future secrets."""
    fed, store, okey = _env(tmp_path)
    wp = FakeWoodpecker(org_secrets={"org_tok": {"push"}}, repos=[])
    rec = _rec(fed, store, okey, woodpecker=wp)
    assert rec.plan(["woodpecker"]).is_empty  # nothing actionable by default
    plan = rec.plan(["woodpecker"], adopt_values=True)
    assert [(a.op, a.id) for a in plan.actions] == [("refresh-value", "org_tok")]
    assert "adopting" in plan.actions[0].detail["reason"]

    rec.apply(["woodpecker"], skip_canary=True, adopt_values=True)
    assert not [n for n in rec.plan(["woodpecker"]).notes if n.kind == "unverifiable"]
    # And now staleness is detectable for a secret that predated the ledger.
    _put(store, "org_tok", b"rotated-after-adoption")
    assert [a.op for a in rec.plan(["woodpecker"]).actions] == ["refresh-value"]


def test_adopt_values_is_off_by_default(tmp_path) -> None:
    # A default that re-pushed the whole credential plane would be a surprise with a
    # real blast radius — and during a staged rotation it would push a value that has
    # not been cut over.
    fed, store, okey = _env(tmp_path)
    wp = FakeWoodpecker(org_secrets={"org_tok": {"push"}}, repos=[])
    rec = _rec(fed, store, okey, woodpecker=wp)
    rec.apply(["woodpecker"], skip_canary=True)
    assert wp.deployed_value("org_tok") is None  # nothing was pushed


def test_a_valueless_entry_is_not_reported_as_unverifiable(tmp_path) -> None:
    """A ``jwt-minted``-shaped entry has no stored value, so there is nothing that
    could be stale. Reporting it would be noise that teaches operators to skim —
    and F-35 was exactly the cost of a plane nobody reads."""
    fed, store, okey = _env(tmp_path)
    (fed / "mgf-standard" / "SECRETS.toml").write_text(MINTED_REGISTRY, encoding="utf-8")
    # Both already deployed, so nothing is planned; only the NOTES are under test.
    wp = FakeWoodpecker(org_secrets={"minted": {"push"}, "org_tok": {"push"}}, repos=[])
    plan = _rec(fed, store, okey, woodpecker=wp).plan(["woodpecker"])
    assert not [n for n in plan.notes if n.kind == "unverifiable" and n.id == "minted"]


def test_a_rescope_update_does_not_disturb_the_deployed_value(tmp_path) -> None:
    # An event-scope change must keep sending value=None. Re-sending the value on
    # every re-scope would push whatever the store holds during a staged rotation.
    fed, store, okey = _env(tmp_path)
    wp = FakeWoodpecker(org_secrets={}, repos=[])
    rec = _rec(fed, store, okey, woodpecker=wp)
    rec.apply(["woodpecker"], skip_canary=True)
    wp._org["org_tok"] = type(wp._org["org_tok"])(frozenset({"tag"}), ())  # drifted scope
    rec.apply(["woodpecker"], skip_canary=True)
    assert wp.org_secret_states()["org_tok"].events == frozenset({"push"})  # re-scoped
    assert wp.deployed_value("org_tok") == b"the-original-value"  # value untouched


# --------------------------------------------------------------------------- #
# Ledger mechanics.
# --------------------------------------------------------------------------- #


def test_the_ledger_records_no_secret_material(tmp_path) -> None:
    """AG-18. The ledger is an id, a surface target, a ciphertext hash and a time —
    the plaintext must not be recoverable or even present."""
    fed, store, okey = _env(tmp_path)
    wp = FakeWoodpecker(org_secrets={}, repos=[])
    _rec(fed, store, okey, woodpecker=wp).apply(["woodpecker"], skip_canary=True)
    raw = store_mod.deployments_path(store).read_text(encoding="utf-8")
    assert b"the-original-value".decode() not in raw
    rec = store_mod.deployments_load(store)["deployments"]["org_tok"]["org:org_tok"]
    assert set(rec) == {"sha256", "deployed_at"}
    assert rec["sha256"] == store_mod.manifest_sha(store, "org_tok")


def test_a_corrupt_ledger_degrades_to_unverifiable_never_raises(tmp_path) -> None:
    # Losing the ledger costs visibility, never correctness: it must not block a
    # reconcile. A hard failure here would make a damaged file an outage.
    fed, store, okey = _env(tmp_path)
    wp = FakeWoodpecker(org_secrets={}, repos=[])
    rec = _rec(fed, store, okey, woodpecker=wp)
    rec.apply(["woodpecker"], skip_canary=True)
    store_mod.deployments_path(store).write_text("{ not json", encoding="utf-8")
    plan = rec.plan(["woodpecker"])
    assert plan.is_empty
    assert any(n.kind == "unverifiable" for n in plan.notes)


def test_absent_ledger_is_an_empty_ledger(tmp_path) -> None:
    assert store_mod.deployments_load(tmp_path) == {"version": 1, "deployments": {}}
    assert store_mod.deployed_sha(store_mod.empty_deployments(), "x", "org:x") is None
