"""Token management with tiered storage fallback.

Storage tiers (in order of preference):
1. OS Keyring (macOS Keychain, Windows Credential Locker, Linux Secret Service)
2. Encrypted file (~/.botvegas/tokens.enc)
3. Runtime memory only (lost on restart)

Usage:
    token_manager = TokenManager()
    token_manager.store("SharkBot", "jwt_token_here")
    token = token_manager.get("SharkBot")
"""

import base64
import json
import logging
import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Literal

logger = logging.getLogger(__name__)

StorageTier = Literal["keyring", "encrypted_file", "memory"]

KEYRING_SERVICE = "botvegas-mcp"
DEFAULT_TOKEN_FILE = Path.home() / ".botvegas" / "tokens.enc"


class EncryptedFileStorage:
    """Simple encrypted JSON file for token storage.
    
    Uses Fernet symmetric encryption (AES-128-CBC + HMAC-SHA256).
    Key derived from password using PBKDF2 with 100k iterations.
    """

    def __init__(self, file_path: Path, password: str | None = None):
        self.file_path = file_path
        self.password = password or os.getenv("BOTVEGAS_TOKEN_PASSWORD", "botvegas-default-key")
        self._fernet = None

    def _create_fernet(self):
        """Create Fernet cipher with key derived from password."""
        if self._fernet:
            return self._fernet

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

        salt = b"botvegas-mcp-token-salt"
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=100_000,
        )
        key = base64.urlsafe_b64encode(kdf.derive(self.password.encode()))
        self._fernet = Fernet(key)
        return self._fernet

    def load(self) -> dict[str, str]:
        """Load and decrypt tokens from file."""
        if not self.file_path.exists():
            return {}
        encrypted = self.file_path.read_bytes()
        fernet = self._create_fernet()
        decrypted = fernet.decrypt(encrypted)
        return json.loads(decrypted)

    def save(self, tokens: dict[str, str]) -> None:
        """Encrypt and save tokens to file."""
        self.file_path.parent.mkdir(parents=True, exist_ok=True)
        fernet = self._create_fernet()
        encrypted = fernet.encrypt(json.dumps(tokens).encode())
        self.file_path.write_bytes(encrypted)

    def get(self, handle: str) -> str | None:
        """Get a single token."""
        return self.load().get(handle)

    def set(self, handle: str, token: str) -> None:
        """Set a single token."""
        tokens = self.load()
        tokens[handle] = token
        self.save(tokens)

    def delete(self, handle: str) -> None:
        """Delete a single token."""
        tokens = self.load()
        tokens.pop(handle, None)
        self.save(tokens)


@dataclass
class TokenManager:
    """Manages player tokens with tiered storage fallback.
    
    Automatically selects the best available storage:
    1. OS Keyring (most secure, requires desktop environment)
    2. Encrypted file (works headless, requires BOTVEGAS_TOKEN_PASSWORD env var)
    3. Runtime memory (always works, lost on restart)
    """

    # Runtime cache (always available)
    _cache: dict[str, str] = field(default_factory=dict)

    # Active storage tier (set during init)
    _tier: StorageTier = field(default="memory", init=False)

    # Storage backends (initialized lazily)
    _keyring_available: bool = field(default=False, init=False)
    _file_storage: EncryptedFileStorage | None = field(default=None, init=False)

    def __post_init__(self):
        """Initialize storage backends."""
        self._init_storage()

    def _init_storage(self) -> None:
        """Try to initialize storage tiers in order of preference."""
        # Tier 1: Try OS keyring
        if self._try_init_keyring():
            self._tier = "keyring"
            logger.info("TokenManager: Using OS keyring storage")
            self._load_from_keyring()
            return

        # Tier 2: Try encrypted file
        if self._try_init_encrypted_file():
            self._tier = "encrypted_file"
            logger.info(f"TokenManager: Using encrypted file storage at {DEFAULT_TOKEN_FILE}")
            self._load_from_file()
            return

        # Tier 3: Memory only
        self._tier = "memory"
        logger.warning("TokenManager: Using memory-only storage (tokens lost on restart)")

    def _try_init_keyring(self) -> bool:
        """Test if OS keyring is available and functional."""
        try:
            import keyring
            from keyring.backends.fail import Keyring as FailKeyring

            # Get the active keyring backend
            kr = keyring.get_keyring()
            
            # Check if it's the fail backend (no real keyring available)
            if isinstance(kr, FailKeyring):
                logger.debug("Keyring backend is FailKeyring, skipping")
                return False

            # Try a test operation to verify it actually works
            keyring.get_password(KEYRING_SERVICE, "__probe__")
            self._keyring_available = True
            return True
        except Exception as e:
            logger.debug(f"Keyring unavailable: {e}")
            return False

    def _try_init_encrypted_file(self) -> bool:
        """Test if encrypted file storage is available."""
        try:
            # Check if cryptography is available
            from cryptography.fernet import Fernet  # noqa: F401

            self._file_storage = EncryptedFileStorage(DEFAULT_TOKEN_FILE)
            # Test that we can create the cipher
            self._file_storage._create_fernet()
            return True
        except ImportError:
            logger.debug("cryptography not installed, encrypted file unavailable")
            return False
        except Exception as e:
            logger.debug(f"Encrypted file storage unavailable: {e}")
            return False

    # =========================================================================
    # Public API
    # =========================================================================

    @property
    def storage_tier(self) -> StorageTier:
        """Return the active storage tier."""
        return self._tier

    def store(self, handle: str, token: str) -> None:
        """Store a token for a player handle."""
        self._cache[handle] = token

        if self._tier == "keyring":
            self._save_to_keyring(handle, token)
        elif self._tier == "encrypted_file" and self._file_storage:
            self._file_storage.set(handle, token)

    def get(self, handle: str) -> str | None:
        """Get a token by player handle."""
        return self._cache.get(handle)

    def remove(self, handle: str) -> None:
        """Remove a token."""
        self._cache.pop(handle, None)

        if self._tier == "keyring":
            self._remove_from_keyring(handle)
        elif self._tier == "encrypted_file" and self._file_storage:
            self._file_storage.delete(handle)

    def list_handles(self) -> list[str]:
        """List all stored handles."""
        return list(self._cache.keys())

    # =========================================================================
    # Keyring Operations
    # =========================================================================

    def _save_to_keyring(self, handle: str, token: str) -> None:
        """Save a token to the OS keyring."""
        try:
            import keyring

            keyring.set_password(KEYRING_SERVICE, handle, token)
            # Also save handle list for enumeration
            self._save_handle_list_to_keyring()
        except Exception as e:
            logger.warning(f"Failed to save to keyring: {e}")

    def _remove_from_keyring(self, handle: str) -> None:
        """Remove a token from the OS keyring."""
        try:
            import keyring

            keyring.delete_password(KEYRING_SERVICE, handle)
            self._save_handle_list_to_keyring()
        except Exception as e:
            logger.warning(f"Failed to remove from keyring: {e}")

    def _load_from_keyring(self) -> None:
        """Load all tokens from keyring into cache."""
        try:
            import keyring

            # Load handle list
            handles_json = keyring.get_password(KEYRING_SERVICE, "__handles__")
            if handles_json:
                handles = json.loads(handles_json)
                for handle in handles:
                    token = keyring.get_password(KEYRING_SERVICE, handle)
                    if token:
                        self._cache[handle] = token
                logger.info(f"Loaded {len(self._cache)} tokens from keyring")
        except Exception as e:
            logger.warning(f"Failed to load from keyring: {e}")

    def _save_handle_list_to_keyring(self) -> None:
        """Save the list of handles to keyring for enumeration."""
        try:
            import keyring

            handles = list(self._cache.keys())
            keyring.set_password(KEYRING_SERVICE, "__handles__", json.dumps(handles))
        except Exception as e:
            logger.warning(f"Failed to save handle list: {e}")

    # =========================================================================
    # File Operations
    # =========================================================================

    def _load_from_file(self) -> None:
        """Load all tokens from encrypted file into cache."""
        if self._file_storage:
            try:
                self._cache = self._file_storage.load()
                logger.info(f"Loaded {len(self._cache)} tokens from encrypted file")
            except Exception as e:
                logger.warning(f"Failed to load from encrypted file: {e}")
