"""Secret store for encrypted secrets management."""

from __future__ import annotations

import os
import stat
from datetime import UTC, datetime
from pathlib import Path

import yaml

from gaol.secrets.crypto import (
    SecretCrypto,
    decode_key,
    encode_key,
    generate_salt,
)
from gaol.secrets.exceptions import (
    SecretExistsError,
    SecretNotFoundError,
    SecretStoreNotInitializedError,
)
from gaol.secrets.keyring import KeyManager, get_key_manager, initialize_master_key
from gaol.secrets.models import (
    EncryptedSecret,
    SecretMetadata,
    SecretsConfig,
    SecretsStore,
    validate_secret_name,
)


def _get_secrets_dir() -> Path:
    """Get the secrets directory path."""
    from gaol.config import get_config_dir

    return get_config_dir() / "secrets"


class SecretStore:
    """Persistent storage for encrypted secrets.

    Secrets are encrypted with AES-256-GCM using a master key stored in
    the system keyring. The encrypted secrets are stored in a YAML file.
    """

    def __init__(self, store_dir: Path | None = None) -> None:
        """Initialize the secret store.

        Args:
            store_dir: Directory for secrets storage. Defaults to
                       ~/.config/gaol/secrets/
        """
        self._store_dir = store_dir or _get_secrets_dir()
        self._store_path = self._store_dir / "secrets.yaml"
        self._key_manager: KeyManager | None = None
        self._crypto: SecretCrypto | None = None

    def _ensure_dir(self) -> None:
        """Ensure the secrets directory exists with proper permissions."""
        self._store_dir.mkdir(parents=True, exist_ok=True)
        # Set directory permissions to 700 (user only)
        os.chmod(self._store_dir, stat.S_IRWXU)

    def _load_store(self) -> SecretsStore:
        """Load the secrets store from disk."""
        if not self._store_path.exists():
            raise SecretStoreNotInitializedError()

        with open(self._store_path) as f:
            data = yaml.safe_load(f)

        return SecretsStore(**data)

    def _save_store(self, store: SecretsStore) -> None:
        """Save the secrets store to disk with restricted permissions."""
        self._ensure_dir()

        # Write to temp file first, then rename (atomic)
        temp_path = self._store_path.with_suffix(".tmp")
        with open(temp_path, "w") as f:
            yaml.dump(store.model_dump(mode="json"), f, default_flow_style=False)

        # Set file permissions to 600 (user read/write only)
        os.chmod(temp_path, stat.S_IRUSR | stat.S_IWUSR)

        # Atomic rename
        temp_path.rename(self._store_path)

    def _get_key_manager(self, store: SecretsStore | None = None) -> KeyManager:
        """Get a key manager configured with the store's salt."""
        if store is None:
            store = self._load_store()

        salt = decode_key(store.config.salt)
        return get_key_manager(
            salt=salt,
            kdf_iterations=store.config.kdf_iterations,
        )

    def _get_crypto(self) -> SecretCrypto:
        """Get a crypto instance with the current master key."""
        if self._crypto is not None:
            return self._crypto

        store = self._load_store()
        key_manager = self._get_key_manager(store)
        master_key = key_manager.get_master_key()
        self._crypto = SecretCrypto(master_key)
        return self._crypto

    def is_initialized(self) -> bool:
        """Check if the secrets store has been initialized."""
        return self._store_path.exists()

    def initialize(self, force: bool = False) -> str:
        """Initialize the secrets store with a new master key.

        Args:
            force: If True, reinitialize even if already initialized

        Returns:
            Message describing where the master key was stored

        Raises:
            SecretExistsError: If already initialized and force=False
        """
        if self.is_initialized() and not force:
            raise SecretExistsError("Secrets store already initialized")

        self._ensure_dir()

        # Generate master key and store in keyring
        master_key, source = initialize_master_key()

        # Create config with new salt
        salt = generate_salt()
        config = SecretsConfig(
            version=1,
            salt=encode_key(salt),
            kdf_iterations=600_000,
            encryption_algorithm="AES-256-GCM",
        )

        # Create empty store
        store = SecretsStore(config=config, secrets={})
        self._save_store(store)

        # Cache the crypto instance
        self._crypto = SecretCrypto(master_key)

        return f"Master key stored in {source.value}"

    def create(
        self,
        name: str,
        value: str,
        description: str | None = None,
        force: bool = False,
    ) -> SecretMetadata:
        """Create a new secret.

        Args:
            name: Secret name (must match [A-Za-z_][A-Za-z0-9_]*)
            value: Secret value (plaintext)
            description: Optional description
            force: If True, overwrite existing secret

        Returns:
            Metadata for the created secret

        Raises:
            SecretExistsError: If secret exists and force=False
            InvalidSecretNameError: If name is invalid
        """
        validate_secret_name(name)

        store = self._load_store()

        if name in store.secrets and not force:
            raise SecretExistsError(name)

        crypto = self._get_crypto()
        encrypted_value = crypto.encrypt(value)

        now = datetime.now(UTC)
        secret = EncryptedSecret(
            name=name,
            encrypted_value=encrypted_value,
            created_at=store.secrets[name].created_at if name in store.secrets else now,
            updated_at=now,
            description=description,
        )

        store.add_secret(secret)
        self._save_store(store)

        return SecretMetadata(
            name=secret.name,
            created_at=secret.created_at,
            updated_at=secret.updated_at,
            description=secret.description,
        )

    def get(self, name: str) -> str:
        """Get the decrypted value of a secret.

        Args:
            name: Secret name

        Returns:
            Decrypted secret value

        Raises:
            SecretNotFoundError: If secret doesn't exist
        """
        store = self._load_store()
        secret = store.get_secret(name)

        if secret is None:
            raise SecretNotFoundError(name)

        crypto = self._get_crypto()
        return crypto.decrypt(secret.encrypted_value)

    def list(self) -> list[SecretMetadata]:
        """List all secrets (metadata only, no values).

        Returns:
            List of SecretMetadata for all secrets
        """
        if not self.is_initialized():
            return []

        store = self._load_store()
        return store.list_metadata()

    def delete(self, name: str) -> bool:
        """Delete a secret.

        Args:
            name: Secret name

        Returns:
            True if the secret was deleted

        Raises:
            SecretNotFoundError: If secret doesn't exist
        """
        store = self._load_store()

        if not store.remove_secret(name):
            raise SecretNotFoundError(name)

        self._save_store(store)
        return True

    def exists(self, name: str) -> bool:
        """Check if a secret exists.

        Args:
            name: Secret name

        Returns:
            True if the secret exists
        """
        if not self.is_initialized():
            return False

        store = self._load_store()
        return store.get_secret(name) is not None

    def update(
        self,
        name: str,
        value: str,
        description: str | None = None,
    ) -> SecretMetadata:
        """Update an existing secret.

        Args:
            name: Secret name
            value: New secret value
            description: Optional new description (None keeps existing)

        Returns:
            Updated metadata

        Raises:
            SecretNotFoundError: If secret doesn't exist
        """
        store = self._load_store()
        existing = store.get_secret(name)

        if existing is None:
            raise SecretNotFoundError(name)

        crypto = self._get_crypto()
        encrypted_value = crypto.encrypt(value)

        secret = EncryptedSecret(
            name=name,
            encrypted_value=encrypted_value,
            created_at=existing.created_at,
            updated_at=datetime.now(UTC),
            description=description if description is not None else existing.description,
        )

        store.add_secret(secret)
        self._save_store(store)

        return SecretMetadata(
            name=secret.name,
            created_at=secret.created_at,
            updated_at=secret.updated_at,
            description=secret.description,
        )

    def get_metadata(self, name: str) -> SecretMetadata:
        """Get metadata for a secret without decrypting.

        Args:
            name: Secret name

        Returns:
            Secret metadata

        Raises:
            SecretNotFoundError: If secret doesn't exist
        """
        store = self._load_store()
        secret = store.get_secret(name)

        if secret is None:
            raise SecretNotFoundError(name)

        return SecretMetadata(
            name=secret.name,
            created_at=secret.created_at,
            updated_at=secret.updated_at,
            description=secret.description,
        )


# Global instance
_store: SecretStore | None = None


def get_secret_store() -> SecretStore:
    """Get the global secret store instance."""
    global _store
    if _store is None:
        _store = SecretStore()
    return _store
