"""Vault manager for split secrets.

Manages vault containers that store private keys with no network access.
Other containers can request cryptographic operations via agent socket
forwarding without ever seeing the private keys.
"""

from __future__ import annotations

import subprocess
from datetime import datetime
from pathlib import Path

from gaol.domains.exceptions import (
    VaultAttachmentError,
    VaultExistsError,
    VaultKeyImportError,
    VaultLockedError,
    VaultNotFoundError,
    VaultOperationDeniedError,
)
from gaol.domains.models import (
    VaultAttachment,
    VaultConfig,
    VaultKeyInfo,
    VaultOperation,
    VaultPolicy,
    VaultState,
    VaultType,
)
from gaol.domains.store import DomainsFileStore, get_domain_store


class VaultManager:
    """Manage vault containers for split secrets.

    Vaults are containers with NO network access that store private keys.
    Other containers can request cryptographic operations (sign, decrypt,
    ssh-auth) via agent socket forwarding.

    Example:
        >>> manager = VaultManager()
        >>> config = VaultConfig(name="gpg-vault", vault_type=VaultType.GPG)
        >>> manager.create(config)
        >>> manager.import_key("gpg-vault", "/path/to/private.key")
        >>> manager.allow("gpg-vault", "work-email", [VaultOperation.SIGN])
        >>> manager.attach("work-email", "gpg-vault")
    """

    def __init__(self, store: DomainsFileStore | None = None) -> None:
        """Initialize the vault manager."""
        self._store = store or get_domain_store()

    def _run_cmd(
        self,
        cmd: list[str],
        *,
        check: bool = True,
        capture_output: bool = True,
        input_data: str | None = None,
    ) -> subprocess.CompletedProcess[str]:
        """Run a command and return the result."""
        try:
            result = subprocess.run(
                cmd,
                check=check,
                capture_output=capture_output,
                text=True,
                input=input_data,
            )
            return result
        except subprocess.CalledProcessError as e:
            raise RuntimeError(f"Command failed: {' '.join(cmd)}\n{e.stderr}") from e

    def create(self, config: VaultConfig) -> VaultState:
        """Create a new vault container.

        The vault container will have NO network access for security.

        Args:
            config: Vault configuration.

        Returns:
            New vault state.

        Raises:
            VaultExistsError: If vault already exists.
        """
        if self._store.vault_exists(config.name):
            raise VaultExistsError(config.name)

        container_name = f"vault-{config.name}"

        try:
            self._create_container(container_name, config)
            self._install_vault_packages(container_name, config)
            self._configure_vault(container_name, config)

            state = VaultState(
                name=config.name,
                vault_type=config.vault_type,
                container_name=container_name,
                status="stopped",
                created_at=datetime.now(),
                description=config.description,
            )

            self._store.save_vault(state)
            return state

        except Exception as e:
            # Clean up on failure
            self._cleanup_container(container_name)
            raise RuntimeError(f"Failed to create vault: {e}") from e

    def _create_container(self, container_name: str, config: VaultConfig) -> None:
        """Create the underlying nspawn container with NO network."""
        from gaol.models.container import ContainerConfig, NetworkConfig, NetworkMode
        from gaol.runtimes.nspawn import NspawnRuntime

        runtime = NspawnRuntime()

        # Vault containers have NO network access - this is critical for security
        container_config = ContainerConfig(
            name=container_name,
            runtime="nspawn",
            distro=config.distro,
            network=NetworkConfig(mode=NetworkMode.NONE),  # No network!
            private_users=False,  # Need access to create agent sockets
        )

        runtime.create(container_config)

    def _setup_container_dns(self, container_name: str) -> None:
        """Set up DNS resolution in a container.

        When using systemd-nspawn -D without boot mode, the container
        doesn't have access to the host's systemd-resolved stub resolver.
        This method configures DNS to use public resolvers.
        """
        machine_path = Path(f"/var/lib/machines/{container_name}")
        resolv_conf = machine_path / "etc" / "resolv.conf"

        # DNS content with public resolvers
        dns_content = "nameserver 8.8.8.8\nnameserver 1.1.1.1\n"

        # Remove existing file/symlink and create a real file
        self._run_cmd(["sudo", "rm", "-f", str(resolv_conf)], check=False)
        subprocess.run(
            ["sudo", "tee", str(resolv_conf)],
            input=dns_content,
            text=True,
            capture_output=True,
        )

    def _install_vault_packages(
        self, container_name: str, config: VaultConfig
    ) -> None:
        """Install required packages for the vault type."""
        packages: list[str] = []

        if config.vault_type in (VaultType.GPG, VaultType.COMBINED):
            packages.extend(["gnupg", "gpg-agent", "pinentry-curses"])

        if config.vault_type in (VaultType.SSH, VaultType.COMBINED):
            packages.extend(["openssh-client"])

        if not packages:
            return

        machine_path = Path(f"/var/lib/machines/{container_name}")

        # Set up DNS before running apt-get
        self._setup_container_dns(container_name)

        # Temporarily move the nspawn config to allow network during setup
        # The nspawn config sets NetworkVeth=no and other settings that may
        # interfere with systemd-nspawn -D mode
        nspawn_config = Path(f"/etc/systemd/nspawn/{container_name}.nspawn")
        nspawn_backup = Path(f"/tmp/{container_name}.nspawn.bak")
        if nspawn_config.exists():
            self._run_cmd(
                ["sudo", "mv", str(nspawn_config), str(nspawn_backup)],
                check=False,
            )

        try:
            self._run_cmd(
                ["sudo", "systemd-nspawn", "-D", str(machine_path), "apt-get", "update"],
                capture_output=False,
            )
            self._run_cmd(
                [
                    "sudo", "systemd-nspawn", "-D", str(machine_path),
                    "env", "DEBIAN_FRONTEND=noninteractive",
                    "apt-get", "install", "-y", *packages,
                ],
                capture_output=False,
            )
        finally:
            # Restore nspawn config
            if nspawn_backup.exists():
                self._run_cmd(
                    ["sudo", "mv", str(nspawn_backup), str(nspawn_config)],
                    check=False,
                )

    def _configure_vault(self, container_name: str, config: VaultConfig) -> None:
        """Configure the vault container."""
        machine_path = Path(f"/var/lib/machines/{container_name}")

        if config.vault_type in (VaultType.GPG, VaultType.COMBINED):
            self._configure_gpg_agent(machine_path)

        if config.vault_type in (VaultType.SSH, VaultType.COMBINED):
            self._configure_ssh_agent(machine_path)

    def _configure_gpg_agent(self, machine_path: Path) -> None:
        """Configure GPG agent for socket forwarding."""
        # Create GPG directory and agent config
        gpg_dir = machine_path / "root" / ".gnupg"
        self._run_cmd(["sudo", "mkdir", "-p", str(gpg_dir)])
        self._run_cmd(["sudo", "chmod", "700", str(gpg_dir)])

        # GPG agent config to allow extra socket connections
        gpg_agent_conf = """# Gaol Vault GPG Agent Config
default-cache-ttl 1800
max-cache-ttl 7200
allow-loopback-pinentry
extra-socket /run/gnupg/S.gpg-agent.extra
"""
        conf_path = gpg_dir / "gpg-agent.conf"
        subprocess.run(
            ["sudo", "tee", str(conf_path)],
            input=gpg_agent_conf,
            text=True,
            capture_output=True,
        )

    def _configure_ssh_agent(self, machine_path: Path) -> None:
        """Configure SSH agent for socket forwarding."""
        # Create SSH directory
        ssh_dir = machine_path / "root" / ".ssh"
        self._run_cmd(["sudo", "mkdir", "-p", str(ssh_dir)])
        self._run_cmd(["sudo", "chmod", "700", str(ssh_dir)])

    def _cleanup_container(self, container_name: str) -> None:
        """Clean up a failed container creation."""
        machine_path = Path(f"/var/lib/machines/{container_name}")
        if machine_path.exists():
            self._run_cmd(["sudo", "rm", "-rf", str(machine_path)], check=False)

        nspawn_path = Path(f"/etc/systemd/nspawn/{container_name}.nspawn")
        if nspawn_path.exists():
            self._run_cmd(["sudo", "rm", str(nspawn_path)], check=False)

    def start(self, name: str) -> VaultState:
        """Start a vault container.

        Args:
            name: Vault name.

        Returns:
            Updated vault state.
        """
        state = self._store.get_vault(name)
        if not state:
            raise VaultNotFoundError(name)

        try:
            # Start the container
            self._run_cmd(["sudo", "machinectl", "start", state.container_name])

            # Wait for it to be running
            self._wait_for_container(state.container_name)

            # Start agents
            self._start_agents(state)

            state.status = "running"
            state.started_at = datetime.now()

            self._store.save_vault(state)
            return state

        except Exception as e:
            state.status = "error"
            state.error_message = str(e)
            self._store.save_vault(state)
            raise

    def _wait_for_container(self, container_name: str, timeout: int = 30) -> None:
        """Wait for a container to be running."""
        import time

        start = time.time()
        while time.time() - start < timeout:
            result = self._run_cmd(
                ["sudo", "machinectl", "show", container_name, "--property=State"],
                check=False,
            )
            if result.returncode == 0 and "running" in result.stdout.lower():
                return
            time.sleep(0.5)

        raise RuntimeError(f"Container {container_name} failed to start")

    def _start_agents(self, state: VaultState) -> None:
        """Start GPG/SSH agents inside the vault."""
        if state.vault_type in (VaultType.GPG, VaultType.COMBINED):
            # Start gpg-agent
            self._run_cmd([
                "sudo", "machinectl", "shell", f"root@{state.container_name}",
                "--", "gpgconf", "--launch", "gpg-agent",
            ], check=False)

            # Get socket path
            result = self._run_cmd([
                "sudo", "machinectl", "shell", f"root@{state.container_name}",
                "--", "gpgconf", "--list-dirs", "agent-extra-socket",
            ], check=False)
            if result.returncode == 0:
                state.gpg_agent_socket = result.stdout.strip()

        if state.vault_type in (VaultType.SSH, VaultType.COMBINED):
            # Start ssh-agent
            result = self._run_cmd([
                "sudo", "machinectl", "shell", f"root@{state.container_name}",
                "--", "bash", "-c", "eval $(ssh-agent -a /run/ssh-agent.sock) && echo $SSH_AUTH_SOCK",
            ], check=False)
            if result.returncode == 0:
                state.ssh_agent_socket = "/run/ssh-agent.sock"

    def stop(self, name: str) -> VaultState:
        """Stop a vault container.

        Args:
            name: Vault name.

        Returns:
            Updated vault state.
        """
        state = self._store.get_vault(name)
        if not state:
            raise VaultNotFoundError(name)

        self._run_cmd(
            ["sudo", "machinectl", "stop", state.container_name],
            check=False,
        )

        state.status = "stopped"
        state.gpg_agent_socket = None
        state.ssh_agent_socket = None

        self._store.save_vault(state)
        return state

    def lock(self, name: str) -> VaultState:
        """Lock a vault (clear cached passphrases).

        Args:
            name: Vault name.

        Returns:
            Updated vault state.
        """
        state = self._store.get_vault(name)
        if not state:
            raise VaultNotFoundError(name)

        if state.status != "running":
            raise RuntimeError(f"Vault '{name}' is not running")

        # Clear GPG agent cache
        if state.vault_type in (VaultType.GPG, VaultType.COMBINED):
            self._run_cmd([
                "sudo", "machinectl", "shell", f"root@{state.container_name}",
                "--", "gpgconf", "--reload", "gpg-agent",
            ], check=False)

        # Clear SSH agent keys
        if state.vault_type in (VaultType.SSH, VaultType.COMBINED):
            self._run_cmd([
                "sudo", "machinectl", "shell", f"root@{state.container_name}",
                "--", "ssh-add", "-D",
            ], check=False)

        state.status = "locked"
        state.locked_at = datetime.now()
        self._store.save_vault(state)

        return state

    def unlock(self, name: str) -> VaultState:
        """Unlock a vault.

        Args:
            name: Vault name.

        Returns:
            Updated vault state.
        """
        state = self._store.get_vault(name)
        if not state:
            raise VaultNotFoundError(name)

        if state.status != "locked":
            raise RuntimeError(f"Vault '{name}' is not locked")

        state.status = "running"
        state.locked_at = None
        self._store.save_vault(state)

        return state

    def delete(self, name: str, force: bool = False) -> None:
        """Delete a vault.

        Args:
            name: Vault name.
            force: Force deletion even if containers are attached.
        """
        state = self._store.get_vault(name)
        if not state:
            raise VaultNotFoundError(name)

        # Check for attached containers
        if state.attached_containers and not force:
            raise RuntimeError(
                f"Vault '{name}' has {len(state.attached_containers)} attached containers. "
                "Use --force to delete anyway."
            )

        # Detach all containers
        import contextlib
        for container in list(state.attached_containers):
            with contextlib.suppress(Exception):
                self.detach(container, name)

        # Stop if running
        if state.status in ("running", "locked"):
            self.stop(name)

        # Delete container
        self._cleanup_container(state.container_name)

        # Remove from store
        self._store.delete_vault(name)

    def import_key(
        self,
        vault_name: str,
        key_path: str,
        key_type: VaultType | None = None,
    ) -> VaultKeyInfo:
        """Import a key into a vault.

        Args:
            vault_name: Vault to import into.
            key_path: Path to the key file.
            key_type: Type of key (gpg or ssh). Auto-detected if not specified.

        Returns:
            Information about the imported key.
        """
        state = self._store.get_vault(vault_name)
        if not state:
            raise VaultNotFoundError(vault_name)

        if state.status not in ("running", "locked"):
            raise RuntimeError(f"Vault '{vault_name}' must be running to import keys")

        key_path_obj = Path(key_path)
        if not key_path_obj.exists():
            raise VaultKeyImportError(vault_name, f"Key file not found: {key_path}")

        # Auto-detect key type
        if key_type is None:
            key_type = self._detect_key_type(key_path_obj)

        if key_type == VaultType.GPG:
            return self._import_gpg_key(state, key_path_obj)
        elif key_type == VaultType.SSH:
            return self._import_ssh_key(state, key_path_obj)
        else:
            raise VaultKeyImportError(vault_name, f"Unsupported key type: {key_type}")

    def _detect_key_type(self, key_path: Path) -> VaultType:
        """Detect the type of key from its contents."""
        content = key_path.read_text()[:500]  # Read first 500 chars

        if "-----BEGIN PGP PRIVATE KEY" in content or "-----BEGIN PGP" in content:
            return VaultType.GPG
        elif "-----BEGIN OPENSSH PRIVATE KEY" in content or "-----BEGIN RSA PRIVATE KEY" in content:
            return VaultType.SSH
        elif key_path.suffix in (".gpg", ".asc"):
            return VaultType.GPG
        else:
            return VaultType.SSH  # Default to SSH

    def _import_gpg_key(self, state: VaultState, key_path: Path) -> VaultKeyInfo:
        """Import a GPG key into the vault."""
        # Copy key file into container
        dest_path = f"/tmp/import-key-{key_path.name}"
        self._run_cmd([
            "sudo", "machinectl", "copy-to", state.container_name,
            str(key_path), dest_path,
        ])

        # Import into GPG
        result = self._run_cmd([
            "sudo", "machinectl", "shell", f"root@{state.container_name}",
            "--", "gpg", "--batch", "--import", dest_path,
        ], check=False)

        # Clean up temp file
        self._run_cmd([
            "sudo", "machinectl", "shell", f"root@{state.container_name}",
            "--", "rm", "-f", dest_path,
        ], check=False)

        if result.returncode != 0:
            raise VaultKeyImportError(state.name, result.stderr)

        # Get key info
        key_info = self._get_gpg_key_info(state)
        if key_info:
            state.keys.append(key_info)
            self._store.save_vault(state)
            return key_info

        raise VaultKeyImportError(state.name, "Failed to get imported key info")

    def _import_ssh_key(self, state: VaultState, key_path: Path) -> VaultKeyInfo:
        """Import an SSH key into the vault."""
        # Copy key file into container
        dest_path = f"/root/.ssh/{key_path.name}"
        self._run_cmd([
            "sudo", "machinectl", "copy-to", state.container_name,
            str(key_path), dest_path,
        ])

        # Set permissions
        self._run_cmd([
            "sudo", "machinectl", "shell", f"root@{state.container_name}",
            "--", "chmod", "600", dest_path,
        ])

        # Add to SSH agent
        self._run_cmd([
            "sudo", "machinectl", "shell", f"root@{state.container_name}",
            "--", "bash", "-c", f"SSH_AUTH_SOCK=/run/ssh-agent.sock ssh-add {dest_path}",
        ], check=False)

        # Get key fingerprint
        result = self._run_cmd([
            "sudo", "machinectl", "shell", f"root@{state.container_name}",
            "--", "ssh-keygen", "-lf", dest_path,
        ], check=False)

        key_id = "unknown"
        key_type = "unknown"
        if result.returncode == 0:
            parts = result.stdout.strip().split()
            if len(parts) >= 2:
                key_id = parts[1]
                key_type = parts[-1].strip("()")

        # Get public key
        result = self._run_cmd([
            "sudo", "machinectl", "shell", f"root@{state.container_name}",
            "--", "ssh-keygen", "-yf", dest_path,
        ], check=False)

        public_key = None
        if result.returncode == 0:
            public_key = result.stdout.strip()

        key_info = VaultKeyInfo(
            key_id=key_id,
            key_type=key_type,
            vault_type=VaultType.SSH,
            ssh_public_key=public_key,
        )

        state.keys.append(key_info)
        self._store.save_vault(state)

        return key_info

    def _get_gpg_key_info(self, state: VaultState) -> VaultKeyInfo | None:
        """Get info about the most recently imported GPG key."""
        result = self._run_cmd([
            "sudo", "machinectl", "shell", f"root@{state.container_name}",
            "--", "gpg", "--list-secret-keys", "--keyid-format=long",
        ], check=False)

        if result.returncode != 0:
            return None

        # Parse output to get key info
        lines = result.stdout.strip().split("\n")
        key_id = None
        key_type = None
        user_id = None

        for line in lines:
            if line.startswith("sec"):
                parts = line.split()
                if len(parts) >= 2:
                    # Format: sec   rsa4096/KEYID date
                    type_and_id = parts[1].split("/")
                    if len(type_and_id) == 2:
                        key_type = type_and_id[0]
                        key_id = type_and_id[1]
            elif line.startswith("uid") and key_id:
                # Get user ID from next line
                user_id = line.split("]")[-1].strip() if "]" in line else line[3:].strip()
                break

        if key_id:
            return VaultKeyInfo(
                key_id=key_id,
                key_type=key_type or "unknown",
                vault_type=VaultType.GPG,
                user_id=user_id,
            )

        return None

    def list_keys(self, vault_name: str) -> list[VaultKeyInfo]:
        """List keys in a vault.

        Args:
            vault_name: Vault name.

        Returns:
            List of key information.
        """
        state = self._store.get_vault(vault_name)
        if not state:
            raise VaultNotFoundError(vault_name)

        return state.keys

    def allow(
        self,
        vault_name: str,
        container_name: str,
        operations: list[VaultOperation],
        key_filter: list[str] | None = None,
    ) -> VaultPolicy:
        """Allow a container to perform operations on the vault.

        Args:
            vault_name: Vault name.
            container_name: Container to allow.
            operations: Operations to allow.
            key_filter: Optional list of key IDs to restrict to.

        Returns:
            The created policy.
        """
        state = self._store.get_vault(vault_name)
        if not state:
            raise VaultNotFoundError(vault_name)

        policy = VaultPolicy(
            container_name=container_name,
            vault_name=vault_name,
            allowed_operations=operations,
            key_filter=key_filter or [],
        )

        self._store.save_vault_policy(policy)
        return policy

    def revoke(self, vault_name: str, container_name: str) -> bool:
        """Revoke a container's access to a vault.

        Args:
            vault_name: Vault name.
            container_name: Container to revoke.

        Returns:
            True if policy was removed.
        """
        # Also detach if attached
        self.detach(container_name, vault_name)

        return self._store.delete_vault_policy(container_name, vault_name)

    def attach(self, container_name: str, vault_name: str) -> VaultAttachment:
        """Attach a container to a vault for agent forwarding.

        The container must have a policy allowing it to use the vault.

        Args:
            container_name: Container to attach.
            vault_name: Vault to attach to.

        Returns:
            Attachment record.
        """
        state = self._store.get_vault(vault_name)
        if not state:
            raise VaultNotFoundError(vault_name)

        if state.status == "locked":
            raise VaultLockedError(vault_name)

        if state.status != "running":
            raise VaultAttachmentError(
                container_name, vault_name, "Vault is not running"
            )

        # Check policy
        policy = self._store.get_vault_policy(container_name, vault_name)
        if not policy:
            raise VaultOperationDeniedError(
                container_name, vault_name, "attach",
                "No policy exists for this container"
            )

        try:
            # Set up agent socket forwarding
            attachment = self._setup_agent_forwarding(container_name, state)

            self._store.add_vault_attachment(attachment)
            return attachment

        except Exception as e:
            raise VaultAttachmentError(container_name, vault_name, str(e)) from e

    def _setup_agent_forwarding(
        self, container_name: str, vault: VaultState
    ) -> VaultAttachment:
        """Set up agent socket forwarding to a container."""
        attachment = VaultAttachment(
            container_name=container_name,
            vault_name=vault.name,
        )

        # Get vault container IP/path for socket forwarding
        # We'll use socat to forward the socket

        if vault.gpg_agent_socket:
            # Forward GPG agent socket
            gpg_sock_path = "/run/user/0/gnupg/S.gpg-agent"
            self._run_cmd([
                "sudo", "machinectl", "shell", f"root@{container_name}",
                "--", "mkdir", "-p", "/run/user/0/gnupg",
            ], check=False)

            # Create a forwarding script
            vault_sock = f"/var/lib/machines/{vault.container_name}{vault.gpg_agent_socket}"
            forward_script = f"""
#!/bin/bash
socat UNIX-LISTEN:{gpg_sock_path},fork,mode=0600 UNIX-CONNECT:{vault_sock} &
"""
            self._run_cmd([
                "sudo", "machinectl", "shell", f"root@{container_name}",
                "--", "bash", "-c", f"echo '{forward_script}' > /tmp/gpg-forward.sh && chmod +x /tmp/gpg-forward.sh",
            ], check=False)

            attachment.gpg_socket_path = gpg_sock_path

        if vault.ssh_agent_socket:
            # Forward SSH agent socket
            ssh_sock_path = "/run/ssh-agent-vault.sock"
            vault_sock = f"/var/lib/machines/{vault.container_name}{vault.ssh_agent_socket}"

            self._run_cmd([
                "sudo", "machinectl", "shell", f"root@{container_name}",
                "--", "bash", "-c",
                f"socat UNIX-LISTEN:{ssh_sock_path},fork,mode=0600 UNIX-CONNECT:{vault_sock} &",
            ], check=False)

            attachment.ssh_socket_path = ssh_sock_path

        return attachment

    def detach(self, container_name: str, vault_name: str) -> bool:
        """Detach a container from a vault.

        Args:
            container_name: Container to detach.
            vault_name: Vault to detach from.

        Returns:
            True if detached successfully.
        """
        # Kill forwarding processes
        self._run_cmd([
            "sudo", "machinectl", "shell", f"root@{container_name}",
            "--", "pkill", "-f", "socat.*gnupg",
        ], check=False)

        self._run_cmd([
            "sudo", "machinectl", "shell", f"root@{container_name}",
            "--", "pkill", "-f", "socat.*ssh-agent",
        ], check=False)

        return self._store.remove_vault_attachment(container_name, vault_name)

    def status(self, name: str) -> VaultState:
        """Get vault status.

        Args:
            name: Vault name.

        Returns:
            Vault state.
        """
        state = self._store.get_vault(name)
        if not state:
            raise VaultNotFoundError(name)

        # Update status from runtime
        result = self._run_cmd(
            ["sudo", "machinectl", "show", state.container_name, "--property=State"],
            check=False,
        )

        if result.returncode == 0 and "running" in result.stdout.lower():
            if state.status not in ("running", "locked"):
                state.status = "running"
        else:
            state.status = "stopped"
            state.gpg_agent_socket = None
            state.ssh_agent_socket = None

        self._store.save_vault(state)
        return state

    def list_vaults(self, vault_type: VaultType | None = None) -> list[VaultState]:
        """List all vaults.

        Args:
            vault_type: Optional filter by type.

        Returns:
            List of vault states.
        """
        return self._store.list_vaults(vault_type)

    def check_operation(
        self,
        container_name: str,
        vault_name: str,
        operation: VaultOperation,
        key_id: str | None = None,
    ) -> bool:
        """Check if an operation is allowed by policy.

        Args:
            container_name: Container requesting the operation.
            vault_name: Vault to operate on.
            operation: Operation to perform.
            key_id: Optional key ID for key-specific policies.

        Returns:
            True if operation is allowed.
        """
        policy = self._store.get_vault_policy(container_name, vault_name)
        if not policy:
            return False

        if operation not in policy.allowed_operations:
            return False

        if policy.key_filter and key_id and key_id not in policy.key_filter:
            return False

        return not (policy.expires_at and datetime.now() > policy.expires_at)


# Global instance
_manager: VaultManager | None = None


def get_vault_manager() -> VaultManager:
    """Get the global VaultManager instance."""
    global _manager
    if _manager is None:
        _manager = VaultManager()
    return _manager
