"""
Encryption utilities for sensitive data (e.g., API keys).
Uses Fernet symmetric encryption from cryptography library.
"""

import base64
import logging
import os

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

logger = logging.getLogger(__name__)

# Get encryption key from environment
ENCRYPTION_KEY = os.getenv("ENCRYPTION_KEY")

# Fallback: Generate key from a password (NOT recommended for production)
FALLBACK_PASSWORD = os.getenv("ENCRYPTION_PASSWORD", "default-dev-password-change-in-production")

# Cached Fernet instance (singleton) — avoids re-running PBKDF2 100K iterations per call
_fernet_instance: Fernet | None = None


def _derive_key_from_password(password: str, salt: bytes) -> bytes:
    """Derive a Fernet key from password + salt using PBKDF2."""
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=32,
        salt=salt,
        iterations=100000,
    )
    return base64.urlsafe_b64encode(kdf.derive(password.encode()))


def _get_fernet() -> Fernet:
    """
    Get cached Fernet instance.
    Priority: ENCRYPTION_KEY env var > password-derived key (dev only).
    When using ENCRYPTION_KEY, salt is not needed (key is used directly).
    """
    global _fernet_instance
    if _fernet_instance is not None:
        return _fernet_instance

    if ENCRYPTION_KEY:
        try:
            _fernet_instance = Fernet(ENCRYPTION_KEY.encode())
            return _fernet_instance
        except Exception:
            try:
                _fernet_instance = Fernet(base64.urlsafe_b64decode(ENCRYPTION_KEY))
                return _fernet_instance
            except Exception:
                logger.warning("Invalid ENCRYPTION_KEY format, using fallback")

    # Fallback: Generate from password with random salt (dev only - NOT secure for production)
    logger.warning(
        "⚠️  ENCRYPTION_KEY not set. Using password-based key derivation (NOT secure for production!)"
    )
    # Use random salt — stored in a file so decryption works across restarts
    salt_file = os.path.join(os.path.dirname(__file__), "..", "data", ".encryption_salt")
    os.makedirs(os.path.dirname(salt_file), exist_ok=True)

    if os.path.exists(salt_file):
        with open(salt_file, "rb") as f:
            salt = f.read()
    else:
        salt = os.urandom(16)
        with open(salt_file, "wb") as f:
            f.write(salt)
        logger.info("Generated new encryption salt (saved to %s)", salt_file)

    key = _derive_key_from_password(FALLBACK_PASSWORD, salt)
    _fernet_instance = Fernet(key)
    return _fernet_instance


def encrypt_api_key(api_key: str) -> str:
    """
    Encrypt an API key using Fernet symmetric encryption.

    Args:
        api_key: Plain text API key to encrypt

    Returns:
        Encrypted API key as base64 string

    Raises:
        ValueError: If api_key is empty
        RuntimeError: If encryption fails
    """
    if not api_key or not api_key.strip():
        raise ValueError("API key cannot be empty")

    try:
        fernet = _get_fernet()
        encrypted = fernet.encrypt(api_key.encode())
        return encrypted.decode()
    except Exception as e:
        logger.error(f"Failed to encrypt API key: {e}")
        raise RuntimeError("Failed to encrypt API key") from e


def decrypt_api_key(encrypted_key: str) -> str:
    """
    Decrypt an encrypted API key.

    Args:
        encrypted_key: Encrypted API key (base64 string)

    Returns:
        Decrypted plain text API key

    Raises:
        ValueError: If encrypted_key is empty
        RuntimeError: If decryption fails (wrong key, corrupted data, etc.)
    """
    if not encrypted_key or not encrypted_key.strip():
        raise ValueError("Encrypted key cannot be empty")

    try:
        fernet = _get_fernet()
        decrypted = fernet.decrypt(encrypted_key.encode())
        return decrypted.decode()
    except Exception as e:
        logger.error(f"Failed to decrypt API key: {e}")
        raise RuntimeError("Failed to decrypt API key. Key may be corrupted or encryption key changed.") from e


def mask_api_key(api_key: str) -> str:
    """
    Mask an API key for display (show only first 7 chars and last 4 chars).

    Args:
        api_key: API key to mask

    Returns:
        Masked API key (e.g., "sk-...xxxx")
    """
    if not api_key or len(api_key) < 11:
        return "sk-...xxxx"

    return f"{api_key[:7]}...{api_key[-4:]}"


def validate_openai_key_format(api_key: str) -> bool:
    """
    Validate OpenAI API key format.

    Args:
        api_key: API key to validate

    Returns:
        True if format is valid, False otherwise
    """
    if not api_key or not api_key.strip():
        return False

    # OpenAI keys typically start with "sk-" and are ~51 characters
    key = api_key.strip()
    if not key.startswith("sk-"):
        return False

    if len(key) < 20 or len(key) > 100:  # Reasonable range
        return False

    return True
