"""
Encrypted file-based credential storage as keyring fallback.

Uses Fernet symmetric encryption with a machine-derived key.
Credentials stored at ~/.aicippy/credentials.enc

This module provides a cross-platform fallback when the OS keyring
(macOS Keychain, Windows Credential Manager, Linux SecretService)
is unavailable or fails. The encryption key is derived from
machine-specific data using PBKDF2HMAC with SHA-256.

Note: This is not high-security storage. It prevents casual reading
of credentials but a determined attacker with filesystem access and
knowledge of the derivation scheme could recover the key. The OS
keyring remains the preferred storage backend.
"""

from __future__ import annotations

import base64
import getpass
import json
import platform
from pathlib import Path
from typing import Any

from cryptography.fernet import Fernet, InvalidToken
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

from aicippy.utils.logging import get_logger

logger = get_logger(__name__)

# Fixed iteration count for PBKDF2 key derivation
_PBKDF2_ITERATIONS: int = 480000

# Static passphrase component for key derivation
_KEY_PASSPHRASE: bytes = b"aicippy-credential-store"


class FileCredentialStorage:
    """Encrypted file-based credential storage as keyring fallback.

    Uses Fernet symmetric encryption with a machine-derived key.
    Credentials stored at ~/.aicippy/credentials.enc

    All credentials are stored in a single encrypted JSON file. Each
    credential is keyed by ``{service}:{key}`` to match the keyring
    API semantics (service name + key name).

    Attributes:
        _cred_path: Path to the encrypted credentials file.
        _fernet: Fernet cipher instance for encrypt/decrypt operations.
    """

    __slots__ = ("_cred_path", "_fernet")

    def __init__(self) -> None:
        """Initialize file credential storage.

        Creates the ~/.aicippy directory if it does not exist and
        derives the encryption key from machine-specific data.
        """
        self._cred_path = Path.home() / ".aicippy" / "credentials.enc"
        self._cred_path.parent.mkdir(parents=True, exist_ok=True)

        key = self._derive_key()
        self._fernet = Fernet(key)

        logger.debug(
            "file_credential_storage_initialized",
            path=str(self._cred_path),
        )

    def _derive_key(self) -> bytes:
        """Derive encryption key from machine-specific data.

        Uses PBKDF2HMAC with SHA-256, combining the machine hostname
        and current OS username as salt. This ensures the key is
        unique per machine/user combination.

        Returns:
            URL-safe base64-encoded 32-byte key suitable for Fernet.
        """
        salt = f"{platform.node()}-{getpass.getuser()}-aicippy".encode()
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=_PBKDF2_ITERATIONS,
        )
        key = base64.urlsafe_b64encode(kdf.derive(_KEY_PASSPHRASE))
        return key

    def _load_store(self) -> dict[str, str]:
        """Load and decrypt the credential store from disk.

        Returns:
            Dictionary of ``{service:key} -> value`` mappings.
            Returns empty dict if file does not exist or cannot be
            decrypted.
        """
        if not self._cred_path.exists():
            return {}

        try:
            encrypted_data = self._cred_path.read_bytes()
            decrypted = self._fernet.decrypt(encrypted_data)
            store: dict[str, str] = json.loads(decrypted.decode("utf-8"))
            return store
        except InvalidToken:
            logger.warning(
                "file_credential_store_decrypt_failed",
                reason="invalid_token_or_key_mismatch",
            )
            return {}
        except (json.JSONDecodeError, UnicodeDecodeError) as e:
            logger.warning(
                "file_credential_store_corrupted",
                error=str(e),
            )
            return {}
        except OSError as e:
            logger.warning(
                "file_credential_store_read_failed",
                error=str(e),
            )
            return {}

    def _save_store(self, store: dict[str, str]) -> None:
        """Encrypt and save the credential store to disk.

        Args:
            store: Dictionary of ``{service:key} -> value`` mappings.

        Raises:
            FileStorageError: If the file cannot be written.
        """
        try:
            data = json.dumps(store).encode("utf-8")
            encrypted = self._fernet.encrypt(data)
            # Write atomically via temp file to prevent corruption
            tmp_path = self._cred_path.with_suffix(".tmp")
            tmp_path.write_bytes(encrypted)
            tmp_path.replace(self._cred_path)

            # Set restrictive permissions (owner read/write only)
            try:
                self._cred_path.chmod(0o600)
            except OSError:
                # Windows may not support chmod; acceptable to skip
                pass

        except OSError as e:
            logger.error(
                "file_credential_store_write_failed",
                error=str(e),
            )
            raise FileStorageError(
                f"Failed to write credential store: {e}"
            ) from e

    def store(self, service: str, key: str, value: str) -> None:
        """Store an encrypted credential.

        Args:
            service: Service name (e.g. ``aicippy``).
            key: Key name (e.g. ``tokens``).
            value: Value to store (will be encrypted on disk).

        Raises:
            FileStorageError: If the credential cannot be saved.
        """
        store_key = f"{service}:{key}"
        current = self._load_store()
        current[store_key] = value
        self._save_store(current)
        logger.debug("file_credential_stored", service=service, key=key)

    def get(self, service: str, key: str) -> str | None:
        """Get a decrypted credential.

        Args:
            service: Service name (e.g. ``aicippy``).
            key: Key name (e.g. ``tokens``).

        Returns:
            The decrypted value if found, None otherwise.
        """
        store_key = f"{service}:{key}"
        current = self._load_store()
        value = current.get(store_key)
        if value is not None:
            logger.debug("file_credential_retrieved", service=service, key=key)
        return value

    def delete(self, service: str, key: str) -> None:
        """Delete a credential.

        Silently succeeds if the credential does not exist.

        Args:
            service: Service name (e.g. ``aicippy``).
            key: Key name (e.g. ``tokens``).
        """
        store_key = f"{service}:{key}"
        current = self._load_store()
        if store_key in current:
            del current[store_key]
            self._save_store(current)
            logger.debug("file_credential_deleted", service=service, key=key)


class FileStorageError(Exception):
    """Raised when file-based credential storage operations fail."""
