"""User-perspective tests for ``mgf.common.vault``.

Surfaces exercised:
  - :class:`EnvVault` — stdlib-only, no [vault] extra needed
  - :class:`EnvVaultConfig` typed config (v0.4 Phase D)
  - :func:`register_vault_backend` / :func:`unregister_vault_backend`
  - The discriminator union :class:`BuiltinVaultBackendConfig`

KeyringVault and EncryptedFileVault require ``mgf-common[vault]``.
The dev install always has it; the tests don't lazy-skip.
"""

from __future__ import annotations

from mgf.common.typing import VaultProtocol
from mgf.common.vault import (
    EnvVault,
    EnvVaultConfig,
    register_vault_backend,
    unregister_vault_backend,
)


class TestEnvVaultBasic:
    def test_construct_with_typed_config(self) -> None:
        cfg = EnvVaultConfig(prefix="MYAPP_VAULT_")
        assert cfg.prefix == "MYAPP_VAULT_"
        assert cfg.backend_type == "env"

    def test_round_trip(self, monkeypatch: object) -> None:
        # Env-vault writes go to os.environ; isolate via monkeypatch.
        import os

        # Clean up any previous test leak in env
        for key in list(os.environ.keys()):
            if key.startswith("PUBLICSURFACE_VAULT_"):
                del os.environ[key]

        v = EnvVault(prefix="PUBLICSURFACE_VAULT_")
        v.set("api_key", "secret-value")
        assert v.get("api_key") == "secret-value"
        v.delete("api_key")
        assert v.get("api_key") is None

        # Cleanup any residue
        for key in list(os.environ.keys()):
            if key.startswith("PUBLICSURFACE_VAULT_"):
                del os.environ[key]

    def test_implements_vault_protocol(self) -> None:
        """Runtime-checkable Protocol — important for consumers
        who pass duck-typed test doubles."""
        v = EnvVault(prefix="X_")
        assert isinstance(v, VaultProtocol)


class TestRegistry:
    def test_register_and_unregister_custom_backend(self) -> None:
        @register_vault_backend("public_surface_test")
        def _make(config: dict) -> VaultProtocol:
            return EnvVault(prefix=config.get("prefix", "TEST_"))

        try:
            # Round-trip: factory accepts dict (the supported path
            # for consumer-registered backends in v0.4)
            v = _make({"prefix": "ROUND_"})
            assert isinstance(v, VaultProtocol)
        finally:
            unregister_vault_backend("public_surface_test")

    def test_unregister_unknown_is_noop(self) -> None:
        # Must not raise.
        unregister_vault_backend("does_not_exist_v1")

    def test_register_with_config_model_typed_path(self) -> None:
        """Custom backends MAY register with a config_model for
        typed validation. Consumer's perspective: pass a dict to
        VaultSettings, get a typed instance back."""
        from typing import Literal

        from pydantic import BaseModel, ConfigDict

        from mgf.common.settings import VaultSettings

        class CustomConfig(BaseModel):
            model_config = ConfigDict(extra="forbid", frozen=True)
            backend_type: Literal["public_surface_typed"] = "public_surface_typed"
            url: str
            timeout: float = 5.0

        @register_vault_backend(
            "public_surface_typed",
            config_model=CustomConfig,
        )
        def _make(config: CustomConfig) -> VaultProtocol:
            assert isinstance(config, CustomConfig)
            return EnvVault(prefix="TYPED_")

        try:
            settings = VaultSettings(
                backend="public_surface_typed",
                config={"url": "https://example.com"},
            )
            assert isinstance(settings.config, CustomConfig)
            assert settings.config.url == "https://example.com"
            assert settings.config.timeout == 5.0
        finally:
            unregister_vault_backend("public_surface_typed")
