"""Key provider implementations for the secrets module.

Supports multiple backends for master key storage:
1. System keyring (GNOME Keyring, macOS Keychain, Windows Credential Manager)
2. Environment variable (GAOL_MASTER_KEY)
3. Interactive password prompt with PBKDF2 key derivation
"""

from __future__ import annotations

import getpass
import os
from abc import ABC, abstractmethod
from enum import Enum
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    pass

from gaol.secrets.crypto import (
    decode_key,
    derive_key_from_password,
    encode_key,
    generate_master_key,
)
from gaol.secrets.exceptions import MasterKeyNotAvailableError

# Constants
SERVICE_NAME = "gaol"
ACCOUNT_NAME = "master_key"
ENV_VAR_NAME = "GAOL_MASTER_KEY"


class KeySource(str, Enum):
    """Source of the master key."""

    KEYRING = "keyring"
    ENVIRONMENT = "environment"
    PROMPT = "prompt"


class KeyProvider(ABC):
    """Abstract base class for key providers."""

    @abstractmethod
    def is_available(self) -> bool:
        """Check if this key provider is available."""
        ...

    @abstractmethod
    def get_master_key(self) -> bytes:
        """Retrieve the master key.

        Raises:
            MasterKeyNotAvailableError: If the key cannot be obtained
        """
        ...

    @abstractmethod
    def set_master_key(self, key: bytes) -> None:
        """Store a master key.

        Raises:
            MasterKeyNotAvailableError: If the key cannot be stored
        """
        ...

    @abstractmethod
    def delete_master_key(self) -> bool:
        """Delete the stored master key.

        Returns:
            True if the key was deleted, False if it didn't exist
        """
        ...

    @property
    @abstractmethod
    def source(self) -> KeySource:
        """Return the source type of this provider."""
        ...


class KeyringProvider(KeyProvider):
    """System keyring provider using the 'keyring' library."""

    @property
    def source(self) -> KeySource:
        return KeySource.KEYRING

    def is_available(self) -> bool:
        """Check if system keyring is accessible."""
        try:
            import keyring
            from keyring.backends.fail import Keyring as FailKeyring

            backend = keyring.get_keyring()
            # Check if we have a real backend, not the fail backend
            return not isinstance(backend, FailKeyring)
        except Exception:
            return False

    def get_master_key(self) -> bytes:
        """Retrieve master key from system keyring."""
        try:
            import keyring

            encoded = keyring.get_password(SERVICE_NAME, ACCOUNT_NAME)
            if encoded is None:
                raise MasterKeyNotAvailableError(
                    "Master key not found in system keyring. "
                    "Run 'gaol secret init' to create one."
                )
            return decode_key(encoded)
        except MasterKeyNotAvailableError:
            raise
        except Exception as e:
            raise MasterKeyNotAvailableError(
                f"Failed to retrieve key from keyring: {e}"
            ) from e

    def set_master_key(self, key: bytes) -> None:
        """Store master key in system keyring."""
        try:
            import keyring

            keyring.set_password(SERVICE_NAME, ACCOUNT_NAME, encode_key(key))
        except Exception as e:
            raise MasterKeyNotAvailableError(
                f"Failed to store key in keyring: {e}"
            ) from e

    def delete_master_key(self) -> bool:
        """Delete master key from system keyring."""
        try:
            import keyring

            # Check if key exists first
            if keyring.get_password(SERVICE_NAME, ACCOUNT_NAME) is None:
                return False
            keyring.delete_password(SERVICE_NAME, ACCOUNT_NAME)
            return True
        except Exception:
            return False


class EnvironmentProvider(KeyProvider):
    """Environment variable key provider.

    Reads the master key from GAOL_MASTER_KEY environment variable.
    The key should be base64 or hex encoded.
    """

    @property
    def source(self) -> KeySource:
        return KeySource.ENVIRONMENT

    def is_available(self) -> bool:
        """Check if the environment variable is set."""
        return ENV_VAR_NAME in os.environ

    def get_master_key(self) -> bytes:
        """Get master key from environment variable."""
        value = os.environ.get(ENV_VAR_NAME)
        if not value:
            raise MasterKeyNotAvailableError(
                f"Environment variable {ENV_VAR_NAME} is not set"
            )

        try:
            # Try base64 first
            return decode_key(value)
        except Exception:
            pass

        try:
            # Try hex encoding
            return bytes.fromhex(value)
        except Exception:
            pass

        raise MasterKeyNotAvailableError(
            f"Invalid key format in {ENV_VAR_NAME}. "
            "Expected base64 or hex encoded key."
        )

    def set_master_key(self, _key: bytes) -> None:
        """Cannot set environment variable from within the process."""
        raise MasterKeyNotAvailableError(
            "Cannot store key in environment variable. "
            f"Set {ENV_VAR_NAME} manually in your shell configuration."
        )

    def delete_master_key(self) -> bool:
        """Cannot delete environment variable from within the process."""
        return False


class PromptProvider(KeyProvider):
    """Interactive password prompt provider.

    Prompts the user for a password and derives a key using PBKDF2.
    Requires a salt to be provided (typically from the secrets config).
    """

    def __init__(self, salt: bytes | None = None, iterations: int = 600_000) -> None:
        """Initialize with salt and iteration count.

        Args:
            salt: Salt for key derivation (required for get_master_key)
            iterations: PBKDF2 iteration count
        """
        self._salt = salt
        self._iterations = iterations

    @property
    def source(self) -> KeySource:
        return KeySource.PROMPT

    def is_available(self) -> bool:
        """Check if we can prompt for input (have a tty)."""
        try:
            import sys

            return sys.stdin.isatty()
        except Exception:
            return False

    def get_master_key(self) -> bytes:
        """Prompt for password and derive key."""
        if self._salt is None:
            raise MasterKeyNotAvailableError(
                "Cannot derive key without salt. "
                "Initialize secrets store first with 'gaol secret init'."
            )

        try:
            password = getpass.getpass("Enter master password: ")
            if not password:
                raise MasterKeyNotAvailableError("Password cannot be empty")
            return derive_key_from_password(password, self._salt, self._iterations)
        except KeyboardInterrupt:
            raise MasterKeyNotAvailableError("Password entry cancelled") from None

    def set_master_key(self, _key: bytes) -> None:
        """Cannot store key with prompt provider."""
        raise MasterKeyNotAvailableError(
            "Password-derived keys are not stored. "
            "Use system keyring for persistent key storage."
        )

    def delete_master_key(self) -> bool:
        """No key to delete for prompt provider."""
        return False


class KeyManager:
    """Manages key providers and provides the master key."""

    def __init__(
        self,
        salt: bytes | None = None,
        kdf_iterations: int = 600_000,
    ) -> None:
        """Initialize with optional salt for password derivation.

        Args:
            salt: Salt for PBKDF2 key derivation (from secrets config)
            kdf_iterations: Number of PBKDF2 iterations
        """
        self._providers: list[KeyProvider] = [
            KeyringProvider(),
            EnvironmentProvider(),
            PromptProvider(salt=salt, iterations=kdf_iterations),
        ]
        self._active_provider: KeyProvider | None = None

    def get_available_providers(self) -> list[KeyProvider]:
        """Get list of available key providers."""
        return [p for p in self._providers if p.is_available()]

    def get_master_key(self) -> bytes:
        """Get the master key from the first available provider.

        Tries providers in order: keyring, environment, prompt.

        Returns:
            The master key bytes

        Raises:
            MasterKeyNotAvailableError: If no provider can provide the key
        """
        errors: list[str] = []

        for provider in self._providers:
            if not provider.is_available():
                continue
            try:
                key = provider.get_master_key()
                self._active_provider = provider
                return key
            except MasterKeyNotAvailableError as e:
                errors.append(f"{provider.source.value}: {e}")
                continue

        raise MasterKeyNotAvailableError(
            "Could not obtain master key from any provider:\n"
            + "\n".join(f"  - {e}" for e in errors)
        )

    def store_master_key(self, key: bytes) -> KeySource:
        """Store a master key in the first available provider.

        Args:
            key: The master key to store

        Returns:
            The source where the key was stored

        Raises:
            MasterKeyNotAvailableError: If no provider can store the key
        """
        for provider in self._providers:
            if not provider.is_available():
                continue
            try:
                provider.set_master_key(key)
                return provider.source
            except MasterKeyNotAvailableError:
                continue

        raise MasterKeyNotAvailableError(
            "Could not store master key. System keyring is not available."
        )

    def delete_master_key(self) -> bool:
        """Delete the master key from all providers.

        Returns:
            True if any key was deleted
        """
        deleted = False
        for provider in self._providers:
            if provider.delete_master_key():
                deleted = True
        return deleted

    @property
    def active_source(self) -> KeySource | None:
        """Return the source of the last retrieved key."""
        return self._active_provider.source if self._active_provider else None


def get_key_manager(
    salt: bytes | None = None,
    kdf_iterations: int = 600_000,
) -> KeyManager:
    """Get a key manager instance.

    Args:
        salt: Salt for PBKDF2 key derivation
        kdf_iterations: Number of PBKDF2 iterations

    Returns:
        A configured KeyManager instance
    """
    return KeyManager(salt=salt, kdf_iterations=kdf_iterations)


def initialize_master_key() -> tuple[bytes, KeySource]:
    """Generate and store a new master key.

    Returns:
        Tuple of (master_key, source where it was stored)

    Raises:
        MasterKeyNotAvailableError: If the key cannot be stored
    """
    key = generate_master_key()
    manager = get_key_manager()
    source = manager.store_master_key(key)
    return key, source
