"""Idempotence property test (SPEC §6.4, §7.3).

For randomly generated valid registries + drifted live states, ``plan → apply →
plan`` yields an EMPTY plan. Deterministic generation (seeded RNG) so a failure
reproduces exactly. The value provider returns a constant fixture (create paths
converge without a real store).
"""

from __future__ import annotations

import random

import pytest

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

_EVENTS = ["push", "pull_request", "tag", "manual", "cron"]


def _entry_toml(name: str, events: list[str]) -> str:
    ev = ", ".join(f'"{e}"' for e in events)
    surf = f'{{ type = "woodpecker-org-secret", events = [{ev}] }}'
    return f"""\
[{name}]
family = "mgf"
kind = "api-token"
purpose = "generated"
consumers = ["x"]
recovery = "re-mint"
state = "active"
policy = {{ allowed_repos = "*", blast_radius = "low" }}
rotation = {{ class = "token", staged = true }}
verify = {{ probe = "auth" }}
surfaces = [ {surf} ]
"""


def _generate(rng: random.Random, tmp_path):
    """A random registry + a FakeWoodpecker with random per-secret drift.

    Sync manages event scopes only; some live secrets carry an out-of-band image
    restriction to prove sync PRESERVES it (never plans a change) and still converges.
    """
    blocks = []
    org_live: dict[str, set[str]] = {}
    org_images: dict[str, list[str]] = {}
    n = rng.randint(1, 8)
    for i in range(n):
        name = f"sec_{i}"
        events = rng.sample(_EVENTS, rng.randint(1, len(_EVENTS)))
        blocks.append(_entry_toml(name, events))
        roll = rng.random()
        if roll < 0.3:
            continue  # missing → create path
        if roll < 0.65:  # wrong scope → update path
            org_live[name] = set(rng.sample(_EVENTS, rng.randint(1, len(_EVENTS))))
        else:  # already correct
            org_live[name] = set(events)
        if rng.random() < 0.4:  # some carry a live (out-of-band) image restriction
            org_images[name] = ["ghcr.io/other:latest"]
    p = tmp_path / "SECRETS.toml"
    p.write_text("\n".join(blocks), encoding="utf-8")
    reg = load([p])
    wp = FakeWoodpecker(org_secrets=org_live, org_images=org_images, repos=[])
    return reg, wp


@pytest.mark.parametrize("seed", range(60))
def test_plan_apply_plan_is_empty(seed, tmp_path) -> None:
    rng = random.Random(seed)
    reg, wp = _generate(rng, tmp_path)
    rec = Reconciler(reg, fed_root=tmp_path, value_provider=lambda _id: b"v", woodpecker=wp)
    rec.apply(["woodpecker"], skip_canary=True)
    residual = rec.plan(["woodpecker"])
    assert residual.is_empty, f"seed {seed}: residual {[a.to_json() for a in residual.actions]}"
