"""v0.4 Phase G5 pin tests — generic Registry primitive."""

from __future__ import annotations

from collections.abc import Callable

import pytest

from mgf.common.registry import Registry, make_registry

# Re-usable factory signature for tests.
WidgetFactory = Callable[[dict[str, object]], str]


class TestRegistryShape:
    """Direct semantics of the generic primitive."""

    def test_construct_via_helper(self) -> None:
        r = make_registry("widget")
        assert isinstance(r, Registry)
        assert r.name == "widget"
        assert len(r) == 0

    def test_register_and_lookup(self) -> None:
        r: Registry[WidgetFactory] = make_registry("widget")

        @r.register("local")
        def _make(config: dict[str, object]) -> str:
            return f"local:{config}"

        assert "local" in r
        assert r.get("local") is _make

    def test_register_returns_original_factory(self) -> None:
        """Decorator returns the wrapped factory unchanged."""
        r: Registry[WidgetFactory] = make_registry("widget")

        @r.register("x")
        def _make(config: dict[str, object]) -> str:
            return "x"

        # The decorated `_make` is callable and is the SAME object
        # that's in the registry.
        assert r.get("x") is _make
        assert _make({}) == "x"

    def test_register_double_raises(self) -> None:
        r: Registry[WidgetFactory] = make_registry("widget")

        @r.register("dup")
        def _a(config: dict[str, object]) -> str:
            return "a"

        with pytest.raises(ValueError, match="already registered"):

            @r.register("dup")
            def _b(config: dict[str, object]) -> str:
                return "b"

    def test_register_double_error_includes_registry_name(self) -> None:
        """The error mentions the registry name so consumers can
        tell which registry is the source."""
        r: Registry[WidgetFactory] = make_registry("vault backend")
        r.register("env")(lambda c: "x")
        with pytest.raises(ValueError, match=r"vault backend.*'env'"):
            r.register("env")(lambda c: "y")

    def test_unregister_known(self) -> None:
        r: Registry[WidgetFactory] = make_registry("widget")
        r.register("known")(lambda c: "x")
        assert "known" in r
        r.unregister("known")
        assert "known" not in r

    def test_unregister_unknown_is_noop(self) -> None:
        r: Registry[WidgetFactory] = make_registry("widget")
        # Must not raise.
        r.unregister("never-registered")

    def test_unregister_then_register_replaces(self) -> None:
        """The supported replacement pattern: unregister first."""
        r: Registry[WidgetFactory] = make_registry("widget")

        @r.register("k")
        def _a(config: dict[str, object]) -> str:
            return "a"

        r.unregister("k")

        @r.register("k")
        def _b(config: dict[str, object]) -> str:
            return "b"

        assert r.get("k") is _b
        assert r.get("k")({}) == "b"  # type: ignore[misc]

    def test_get_unknown_returns_none(self) -> None:
        r: Registry[WidgetFactory] = make_registry("widget")
        assert r.get("never") is None

    def test_snapshot_is_shallow_copy(self) -> None:
        r: Registry[WidgetFactory] = make_registry("widget")
        r.register("k")(lambda c: "x")
        snap = r.snapshot()
        snap["spam"] = lambda c: "spam"  # type: ignore[assignment]
        # Mutating the snapshot MUST NOT affect the live registry.
        assert "spam" not in r

    def test_iter_yields_in_insertion_order(self) -> None:
        r: Registry[WidgetFactory] = make_registry("widget")

        @r.register("first")
        def _a(c: dict[str, object]) -> str:
            return "a"

        @r.register("second")
        def _b(c: dict[str, object]) -> str:
            return "b"

        @r.register("third")
        def _c(c: dict[str, object]) -> str:
            return "c"

        keys = [k for k, _ in r]
        assert keys == ["first", "second", "third"]

    def test_len(self) -> None:
        r: Registry[WidgetFactory] = make_registry("widget")
        assert len(r) == 0
        r.register("a")(lambda c: "a")
        assert len(r) == 1
        r.register("b")(lambda c: "b")
        assert len(r) == 2
        r.unregister("a")
        assert len(r) == 1

    def test_contains(self) -> None:
        r: Registry[WidgetFactory] = make_registry("widget")
        assert "x" not in r
        r.register("x")(lambda c: "x")
        assert "x" in r

    def test_repr_includes_name_and_count(self) -> None:
        r: Registry[WidgetFactory] = make_registry("widget")
        r.register("a")(lambda c: "a")
        r.register("b")(lambda c: "b")
        rep = repr(r)
        assert "widget" in rep
        assert "2 items" in rep


class TestExistingRegistriesUnchanged:
    """Sanity: Phase G5 ships the primitive WITHOUT migrating the
    six existing hand-rolled registries. Their public APIs must
    remain bit-identical."""

    def test_vault_registry_still_uses_hand_rolled_shape(self) -> None:
        from mgf.common.vault import register_vault_backend, unregister_vault_backend

        # The hand-rolled functions must still exist + be callable.
        assert callable(register_vault_backend)
        assert callable(unregister_vault_backend)

    def test_cli_registry_still_uses_hand_rolled_shape(self) -> None:
        from mgf.common.cli import (
            register_cli_subcommand,
            unregister_cli_subcommand,
        )

        assert callable(register_cli_subcommand)
        assert callable(unregister_cli_subcommand)

    def test_crash_sink_registry_still_uses_hand_rolled_shape(self) -> None:
        from mgf.common.observability import (
            register_crash_sink,
            unregister_crash_sink,
        )

        assert callable(register_crash_sink)
        assert callable(unregister_crash_sink)
