"""Keystore implementation using the keyring library.

Provides secure master key storage using OS credential stores.
"""

import base64
import binascii
import os
import secrets
import stat
from pathlib import Path
from typing import Final

import keyring
import keyring.errors

from noteflow.config.constants import APP_DIR_NAME
from noteflow.infrastructure.logging import get_logger

logger = get_logger(__name__)

# Constants
KEY_SIZE: Final[int] = 32  # 256-bit key
SERVICE_NAME: Final[str] = "noteflow"
KEY_NAME: Final[str] = "master_key"
ENV_VAR_NAME: Final[str] = "NOTEFLOW_MASTER_KEY"
DEFAULT_KEY_FILE: Final[Path] = Path.home() / APP_DIR_NAME / ".master_key"


def _decode_and_validate_key(encoded: str, source_name: str) -> bytes:
    """Decode and validate a base64-encoded master key.

    Args:
        encoded: Base64-encoded key string.
        source_name: Human-readable source name for error messages.

    Returns:
        Decoded key bytes.

    Raises:
        RuntimeError: If decoding fails or key size is wrong.
    """
    try:
        decoded = base64.b64decode(encoded, validate=True)
    except (binascii.Error, ValueError) as exc:
        raise RuntimeError(f"{source_name} contains invalid base64") from exc
    if len(decoded) != KEY_SIZE:
        raise RuntimeError(
            f"{source_name} has wrong key size: expected {KEY_SIZE}, got {len(decoded)}"
        )
    return decoded


def _generate_key() -> tuple[bytes, str]:
    """Generate a new random master key.

    Returns:
        Tuple of (raw_key_bytes, base64_encoded_string).
    """
    raw_key = secrets.token_bytes(KEY_SIZE)
    encoded = base64.b64encode(raw_key).decode("ascii")
    return raw_key, encoded


class KeyringKeyStore:
    """keyring-based key storage using OS credential store.

    Uses:
    - macOS: Keychain
    - Windows: Credential Manager
    - Linux: SecretService (GNOME Keyring, KWallet)
    """

    def __init__(
        self,
        service_name: str = SERVICE_NAME,
        key_name: str = KEY_NAME,
    ) -> None:
        """Initialize the keystore.

        Args:
            service_name: Service identifier for keyring.
            key_name: Key identifier within the service.
        """
        self._service_name = service_name
        self._key_name = key_name

    def get_or_create_master_key(self) -> bytes:
        """Retrieve or generate the master encryption key.

        Checks for an environment variable first (for headless/container deployments),
        then falls back to the OS keyring.

        Returns:
            32-byte master key.

        Raises:
            RuntimeError: If keychain is unavailable and no env var is set.
        """
        # Check environment variable first (for headless/container deployments)
        if env_key := os.environ.get(ENV_VAR_NAME):
            logger.debug("Using master key from environment variable")
            return _decode_and_validate_key(env_key, f"Environment variable {ENV_VAR_NAME}")

        try:
            # Try to retrieve existing key from keyring
            stored = keyring.get_password(self._service_name, self._key_name)
            if stored is not None:
                logger.debug("Retrieved existing master key from keyring")
                return _decode_and_validate_key(stored, "Keyring storage")

            # Generate new key
            new_key, encoded = _generate_key()

            # Store in keyring
            keyring.set_password(self._service_name, self._key_name, encoded)
            logger.info("Generated and stored new master key in keyring")
            return new_key

        except keyring.errors.KeyringError as e:
            # Fall back to file-based storage for headless environments
            logger.warning(
                "Keyring unavailable (%s), falling back to file-based key storage",
                e,
            )
            return FileKeyStore().get_or_create_master_key()

    def delete_master_key(self) -> None:
        """Delete the master key from the keychain.

        Safe to call if key doesn't exist.
        """
        try:
            keyring.delete_password(self._service_name, self._key_name)
            logger.info("Deleted master key")
        except keyring.errors.PasswordDeleteError:
            # Key doesn't exist, that's fine
            logger.debug("Master key not found, nothing to delete")
        except keyring.errors.KeyringError as e:
            logger.warning("Failed to delete master key: %s", e)

    def has_master_key(self) -> bool:
        """Check if master key exists in the keychain.

        Returns:
            True if master key exists.
        """
        try:
            stored = keyring.get_password(self._service_name, self._key_name)
            return stored is not None
        except keyring.errors.KeyringError:
            return False

    @property
    def service_name(self) -> str:
        """Get the service name used for keyring."""
        return self._service_name

    @property
    def key_name(self) -> str:
        """Get the key name used for keyring."""
        return self._key_name


class InMemoryKeyStore:
    """In-memory key storage for testing.

    Keys are lost when the process exits.
    """

    def __init__(self) -> None:
        """Initialize the in-memory keystore."""
        self._key: bytes | None = None

    def get_or_create_master_key(self) -> bytes:
        """Retrieve or generate the master encryption key."""
        if self._key is None:
            self._key = secrets.token_bytes(KEY_SIZE)
            logger.debug("Generated in-memory master key")
        return self._key

    def delete_master_key(self) -> None:
        """Delete the master key."""
        self._key = None
        logger.debug("Deleted in-memory master key")

    def has_master_key(self) -> bool:
        """Check if master key exists."""
        return self._key is not None


class FileKeyStore:
    """File-based key storage for headless environments.

    Stores the master key in a restricted-permissions file when keyring is
    unavailable. This is a fallback for headless servers, containers, and
    environments without a desktop session.

    File permissions are set to 0600 (owner read/write only).
    """

    def __init__(self, key_file: Path | None = None) -> None:
        """Initialize the file keystore.

        Args:
            key_file: Path to key file. Defaults to ~/.noteflow/.master_key.
        """
        self._key_file = key_file or DEFAULT_KEY_FILE

    def get_or_create_master_key(self) -> bytes:
        """Retrieve or generate the master encryption key.

        Returns:
            32-byte master key.

        Raises:
            RuntimeError: If key file exists with wrong size or permissions fail.
        """
        if self._key_file.exists():
            logger.debug("Retrieved master key from file: %s", self._key_file)
            content = self._key_file.read_text().strip()
            return _decode_and_validate_key(content, f"Key file {self._key_file}")

        # Generate new key
        new_key, encoded = _generate_key()

        # Create parent directory if needed
        self._key_file.parent.mkdir(parents=True, exist_ok=True)

        # Write key with restricted permissions
        self._key_file.write_text(encoded)
        self._key_file.chmod(stat.S_IRUSR | stat.S_IWUSR)  # 0600

        logger.info("Generated and stored master key in file: %s", self._key_file)
        return new_key

    def delete_master_key(self) -> None:
        """Delete the master key file.

        Safe to call if file doesn't exist.
        """
        if self._key_file.exists():
            self._key_file.unlink()
            logger.info("Deleted master key file: %s", self._key_file)
        else:
            logger.debug("Master key file not found, nothing to delete")

    def has_master_key(self) -> bool:
        """Check if master key file exists.

        Returns:
            True if key file exists.
        """
        return self._key_file.exists()

    @property
    def key_file(self) -> Path:
        """Get the key file path."""
        return self._key_file
