"""Tests for the federation registry seam ``mgf.standard.libraries()``.

This is the agent-facing, drift-proof way to learn the OTHER federation
projects (the roster + dependency graph), resolved from the installed
package rather than a copied list that catch-up/sync had to keep chasing.
The invariants pinned here (the ``mgf-common`` ↔ ``mgf-standard``
extraction edge, the record schema) are stable across releases.
"""

from __future__ import annotations

import mgf.standard as ds


class TestLibrariesSeam:
    def test_returns_nonempty_list_of_dicts(self) -> None:
        libs = ds.libraries()
        assert isinstance(libs, list)
        assert libs, "registry resolved empty"
        assert all(isinstance(x, dict) for x in libs)

    def test_each_entry_has_the_documented_schema(self) -> None:
        for entry in ds.libraries():
            for key in (
                "repo",
                "lang",
                "version",
                "requires_python",
                "deps",
                "used_by",
                "extras",
                "third_party_count",
                "purpose",
            ):
                assert key in entry, f"{entry.get('repo')!r} missing {key!r}"
            assert isinstance(entry["deps"], dict)
            assert isinstance(entry["used_by"], list)

    def test_cornerstone_present_and_depends_on_standard(self) -> None:
        # The extraction's defining edge: mgf-common depends on
        # mgf-standard. A stable invariant to pin the resolver against.
        libs = {x["repo"]: x for x in ds.libraries()}
        assert "mgf-common" in libs
        assert "mgf-standard" in libs["mgf-common"]["deps"]

    def test_reverse_edge_is_populated(self) -> None:
        libs = {x["repo"]: x for x in ds.libraries()}
        assert "mgf-common" in libs["mgf-standard"]["used_by"]

    def test_sorted_by_repo(self) -> None:
        repos = [x["repo"] for x in ds.libraries()]
        assert repos == sorted(repos)


class TestReleaseWavesSeam:
    def test_returns_list_of_sorted_string_waves(self) -> None:
        waves = ds.release_waves()
        assert isinstance(waves, list)
        assert waves, "release_waves resolved empty"
        for wave in waves:
            assert isinstance(wave, list)
            assert all(isinstance(r, str) for r in wave)
            assert wave == sorted(wave)

    def test_covers_exactly_the_roster(self) -> None:
        # Every library appears in exactly one wave — the walk order is a
        # partition of the roster, so nothing is silently dropped.
        flat = [r for wave in ds.release_waves() for r in wave]
        assert len(flat) == len(set(flat)), "a repo appears in two waves"
        assert set(flat) == {x["repo"] for x in ds.libraries()}

    def test_roots_wave_holds_the_cornerstone_and_corpus(self) -> None:
        wave0 = ds.release_waves()[0]
        assert "mgf-common" in wave0
        assert "mgf-standard" in wave0


class TestAppTierCoherence:
    """The registry `kind` key ⇄ `templates/policy.toml [exempt].woodpecker`.

    These two files are one mechanism read by one consumer. `mgf-fed drift-check`
    excuses an exempt member from generation-currency and holds it to a step-name
    fallback instead — and since mgf-fed 0.24.0 it selects WHICH fallback from the
    registry's `kind` (`_roster.Member.kind`, default `"library"`). Get them out
    of step and the failure is silent and inverted: an exempt Python **app** whose
    registry row lost its `kind` is graded against the library step-list
    (`build` / `sbom` / `publish` / `verify-devpi` / `test-3.11`) it structurally
    cannot satisfy, so exempting it produces TWELVE findings where not exempting
    it produced one (mgf-fed PAPER-151, court round L9 §H3).

    Nothing else in either repo pins the pair, so this is the pin.
    """

    @staticmethod
    def _exempt_woodpecker() -> list[str]:
        import tomllib
        from pathlib import Path

        policy = Path(__file__).resolve().parents[2] / "templates" / "policy.toml"
        data = tomllib.loads(policy.read_text(encoding="utf-8"))
        return list(data.get("exempt", {}).get("woodpecker", []))

    def test_kind_is_a_known_tier(self) -> None:
        for entry in ds.libraries():
            kind = entry.get("kind", "library")
            assert kind in {"library", "app"}, f"{entry['repo']}: unknown tier {kind!r}"

    def test_the_default_is_omitted_never_spelled_out(self) -> None:
        # The absent key IS the "library" answer — that is what makes the field
        # back-compatible across the mgf-fed version boundary. A row that spells
        # `"kind": "library"` is noise the emitter is written to prevent.
        for entry in ds.libraries():
            assert entry.get("kind") != "library", (
                f"{entry['repo']}: the library default MUST be omitted, not emitted"
            )

    def test_the_app_tier_is_declared_and_non_empty(self) -> None:
        apps = [e["repo"] for e in ds.libraries() if e.get("kind") == "app"]
        # Vacuity pin (TS-29): a discovered set this suite iterates must be
        # non-empty, or every assertion below passes by looking at nothing.
        assert apps, "no app-tier member in the registry — did the generator drop `kind`?"
        assert "TelePlasma" in apps

    def test_every_app_tier_member_is_exempt_from_generation_currency(self) -> None:
        exempt = self._exempt_woodpecker()
        for entry in ds.libraries():
            if entry.get("kind") == "app":
                assert entry["repo"] in exempt, (
                    f"{entry['repo']} is declared app-tier but is NOT in "
                    "[exempt].woodpecker — it would be held to a generated "
                    "pipeline no template can render"
                )

    def test_an_exempt_python_member_is_either_app_tier_or_a_real_library(self) -> None:
        # The other direction, and the one that bites: an exempt Python member
        # with no `kind = "app"` is graded against the full library step-list.
        # Every exempt Python member here must therefore be one that genuinely
        # HAS those steps (mgf-hrb, mgf-test-supervisor, mgf-standard, …).
        rows = {e["repo"]: e for e in ds.libraries()}
        for repo in self._exempt_woodpecker():
            entry = rows.get(repo)
            if entry is None or entry["lang"] != "python":
                continue  # not in the roster, or non-Python → universal subset already
            if entry.get("kind") == "app":
                continue
            assert repo in {
                "mgf-hrb",
                "mgf-test-supervisor",
                "mgf-standard",
            }, (
                f"{repo} is an exempt Python member with no `kind` — it is being "
                "held to the library step-list. Declare its tier in "
                "scripts/gen_mgf_libraries.py::_APP_TIER_REPOS, or record it here."
            )
