"""``mgf.common.vault`` — credential storage backends.

See: ``docs/reference/vault.md``.

Closes FEEDBACK §3.2. Phase 5 of the v0.3 closed-box reshape.

Three built-in backends, each implementing
:class:`mgf.common.typing.VaultProtocol`:

  - :class:`EnvVault` — stdlib-only; reads / writes
    ``<prefix><KEY>`` env vars. The right backend for CI /
    containerised deployments where the orchestrator injects
    secrets at process-start time. Works WITHOUT the optional
    ``[vault]`` extra installed.

  - :class:`KeyringVault` — system credential store via the
    third-party :mod:`keyring` library (libsecret / Keychain /
    Credential Manager). Requires ``mgf-common[vault]``. The
    desktop default — secrets persist across runs without the
    consumer managing a passphrase.

  - :class:`EncryptedFileVault` — Fernet (AES-128 + HMAC-SHA256)
    encrypted JSON-on-disk; PBKDF2-HMAC-SHA256 with 600 000
    iterations (OWASP 2023 baseline per rule SC-05). Requires
    ``mgf-common[vault]``. The fallback for headless Linux /
    embedded CI where no keyring daemon runs.

Custom backends — third-party stores (HashiCorp Vault, AWS
Secrets Manager) — are added via the registry::

    from mgf.common.vault import register_vault_backend

    @register_vault_backend("hashicorp")
    def make_vault(config):
        ...

The built-in backends are pre-registered at import time:
``"env"`` / ``"keyring"`` / ``"file"``.

Audit logging: every backend emits an ``audit=true`` log record
on ``get`` / ``set`` / ``delete`` per rule SC-17. The KEY is
logged; the VALUE is not.

Closed-box property: consumers DO NOT import :mod:`keyring`,
:mod:`cryptography`, or any backend-specific module directly —
they pass a ``VaultProtocol`` everywhere. Switching backends is
a single line at bootstrap.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from mgf.common.vault._configs import (
    BuiltinVaultBackendConfig,
    EncryptedFileVaultConfig,
    EnvVaultConfig,
    KeyringVaultConfig,
)
from mgf.common.vault._env_vault import EnvVault
from mgf.common.vault._registry import (
    VaultBackendFactory,
    register_vault_backend,
    unregister_vault_backend,
)

if TYPE_CHECKING:
    from mgf.common.typing import VaultProtocol


# ---------------------------------------------------------------------------
# Lazy re-exports of the [vault]-extra backends
# ---------------------------------------------------------------------------
#
# KeyringVault + EncryptedFileVault import the optional
# :mod:`keyring` / :mod:`cryptography` libraries lazily — they
# raise :class:`mgf.common.exceptions.VaultError` (chained from
# the original ImportError) only when CONSTRUCTED without the
# extra installed. Importing the symbol is always safe; just
# don't instantiate without the extra.

from mgf.common.vault._file_vault import EncryptedFileVault
from mgf.common.vault._keyring_vault import KeyringVault

# ---------------------------------------------------------------------------
# Built-in backend pre-registration
# ---------------------------------------------------------------------------
#
# These three names are always available in the registry so
# downstream consumers (and the future ``open_vault(settings)``
# helper) can construct a backend by name without forking on
# the built-in vs. consumer-registered path.


@register_vault_backend("env", config_model=EnvVaultConfig)
def _make_env_vault(config: EnvVaultConfig) -> VaultProtocol:
    """Built-in env-var backend factory. Pre-registered.

    Accepts :class:`EnvVaultConfig` — pydantic enforces the shape at
    construction time. A ``dict[str, Any]`` passed at the
    :class:`mgf.common.settings.VaultSettings` layer is validated
    into ``EnvVaultConfig`` by the settings model validator before
    reaching this factory.

    The pre-v1.0 cleanup removed the legacy bare-dict shape that was
    handled inside the factory; the typed config is the only
    accepted shape now.
    """
    return EnvVault(prefix=config.prefix)


@register_vault_backend("keyring", config_model=KeyringVaultConfig)
def _make_keyring_vault(config: KeyringVaultConfig) -> VaultProtocol:
    """Built-in system-keyring backend factory. Pre-registered.

    Accepts :class:`KeyringVaultConfig`. Requires the ``[vault]``
    extra; raises :class:`mgf.common.exceptions.VaultError` on
    construction if the extra is not installed.
    """
    return KeyringVault(service=config.service)


@register_vault_backend("file", config_model=EncryptedFileVaultConfig)
def _make_file_vault(config: EncryptedFileVaultConfig) -> VaultProtocol:
    """Built-in encrypted-file backend factory. Pre-registered.

    Accepts :class:`EncryptedFileVaultConfig` — ``path`` and
    ``passphrase`` are required pydantic fields, so missing-arg
    failure is at construction time.

    Requires the ``[vault]`` extra; raises
    :class:`mgf.common.exceptions.VaultError` on construction if the
    extra is not installed.
    """
    # Unwrap the SecretStr only here, at the crypto boundary (CF-05/SC-01) —
    # the runtime vault holds the plain passphrase transiently for the KDF;
    # it is never serialised.
    return EncryptedFileVault(config.path, passphrase=config.passphrase.get_secret_value())


__all__ = [
    "BuiltinVaultBackendConfig",
    "EncryptedFileVault",
    "EncryptedFileVaultConfig",
    "EnvVault",
    "EnvVaultConfig",
    "KeyringVault",
    "KeyringVaultConfig",
    "VaultBackendFactory",
    "register_vault_backend",
    "unregister_vault_backend",
]
