"""
SQLCipher utility functions for consistent database operations.

This module centralizes all SQLCipher-specific operations to ensure
consistent password handling and PRAGMA settings across the codebase.
"""

from typing import Any, Optional
from hashlib import pbkdf2_hmac
from functools import cache

from loguru import logger


@cache
def _get_key_from_password(password: str) -> bytes:
    """
    Generates an encryption key from the user's password.

    Args:
        password: The password.

    Returns:
        The generated key.

    """
    # Generate a secure key based on the password.
    settings = get_sqlcipher_settings()
    logger.info("Generating DB encryption key...")

    # The salt we use here doesn't actually matter because sqlcipher
    # automatically salts the encryption key.
    key = pbkdf2_hmac(
        "sha512", password.encode(), b"no salt", settings["kdf_iterations"]
    )

    logger.info("Generated DB encryption key.")

    return key


def set_sqlcipher_key(cursor_or_conn: Any, password: str) -> None:
    """
    Set the SQLCipher encryption key using hexadecimal encoding.

    This avoids SQL injection and escaping issues with special characters.

    Args:
        cursor_or_conn: SQLCipher cursor or connection object
        password: The password to use for encryption

    """
    key = _get_key_from_password(password)
    cursor_or_conn.execute(f"PRAGMA key = \"x'{key.hex()}'\"")


def set_sqlcipher_rekey(cursor_or_conn: Any, new_password: str) -> None:
    """
    Change the SQLCipher encryption key using hexadecimal encoding.

    Args:
        cursor_or_conn: SQLCipher cursor or connection object
        new_password: The new password to use for encryption
    """
    hex_password = new_password.encode("utf-8").hex()

    # Handle SQLAlchemy connections that need text()
    if hasattr(cursor_or_conn, "execute") and hasattr(
        cursor_or_conn.execute, "__self__"
    ):
        # This is likely a SQLAlchemy connection
        from sqlalchemy import text

        # SQLAlchemy doesn't support parameterized PRAGMA, so we use the safe hex encoding
        # The hex encoding already prevents injection since it only contains [0-9a-f]
        safe_sql = f"PRAGMA rekey = \"x'{hex_password}'\""
        cursor_or_conn.execute(text(safe_sql))
    else:
        # Raw SQLCipher connection - also doesn't support parameterized PRAGMA
        # The hex encoding already prevents injection since it only contains [0-9a-f]
        safe_sql = f"PRAGMA rekey = \"x'{hex_password}'\""
        cursor_or_conn.execute(safe_sql)


# Default SQLCipher configuration (can be overridden by settings)
# Reduced for testing - in production use higher values
DEFAULT_KDF_ITERATIONS = 256000  # Reduced for testing (was 256000)
DEFAULT_PAGE_SIZE = 16384  # 16KB pages for maximum performance with caching
DEFAULT_HMAC_ALGORITHM = "HMAC_SHA512"
DEFAULT_KDF_ALGORITHM = "PBKDF2_HMAC_SHA512"


def get_sqlcipher_settings(username: Optional[str] = None) -> dict:
    """
    Get SQLCipher settings from environment variables or use defaults.

    These settings cannot be changed after database creation, so they
    must be configured via environment variables only.

    Args:
        username: Username to get settings for (not used anymore)

    Returns:
        Dictionary with SQLCipher configuration
    """
    import os

    settings = {
        "kdf_iterations": int(
            os.environ.get("LDR_DB_KDF_ITERATIONS", DEFAULT_KDF_ITERATIONS)
        ),
        "page_size": int(os.environ.get("LDR_DB_PAGE_SIZE", DEFAULT_PAGE_SIZE)),
        "hmac_algorithm": os.environ.get(
            "LDR_DB_HMAC_ALGORITHM", DEFAULT_HMAC_ALGORITHM
        ),
        "kdf_algorithm": os.environ.get(
            "LDR_DB_KDF_ALGORITHM", DEFAULT_KDF_ALGORITHM
        ),
    }

    return settings


def apply_sqlcipher_pragmas(
    cursor_or_conn: Any,
    creation_mode: bool = False,
    username: Optional[str] = None,
) -> None:
    """
    Apply standard SQLCipher PRAGMA settings.

    Args:
        cursor_or_conn: SQLCipher cursor or connection object
        creation_mode: If True, applies settings for database creation.
                      If False, applies settings for existing database access.
        username: Username to get settings for (if available)
    """
    # Get settings (from database if available, otherwise defaults)
    settings = get_sqlcipher_settings(username)

    # Core settings needed for both creation and access
    cursor_or_conn.execute(f"PRAGMA cipher_page_size = {settings['page_size']}")
    cursor_or_conn.execute(
        f"PRAGMA cipher_hmac_algorithm = {settings['hmac_algorithm']}"
    )
    cursor_or_conn.execute(f"PRAGMA kdf_iter = {settings['kdf_iterations']}")

    if creation_mode:
        # Additional settings only needed during creation
        cursor_or_conn.execute(
            "PRAGMA cipher_memory_security = OFF"
        )  # Better performance


def apply_performance_pragmas(
    cursor_or_conn: Any, username: Optional[str] = None
) -> None:
    """
    Apply performance-related PRAGMA settings from environment variables.

    These settings cannot be changed after database creation, so they
    must be configured via environment variables only.

    Args:
        cursor_or_conn: SQLCipher cursor or connection object
        username: Username to get settings for (not used anymore)
    """
    import os

    # Default values that are always applied
    cursor_or_conn.execute("PRAGMA temp_store = MEMORY")
    cursor_or_conn.execute("PRAGMA busy_timeout = 10000")  # 10 second timeout

    # Get settings from environment variables
    cache_mb = int(os.environ.get("LDR_DB_CACHE_SIZE_MB", "64"))
    cache_pages = -(cache_mb * 1024)  # Negative for KB cache size
    cursor_or_conn.execute(f"PRAGMA cache_size = {cache_pages}")

    journal_mode = os.environ.get("LDR_DB_JOURNAL_MODE", "WAL")
    cursor_or_conn.execute(f"PRAGMA journal_mode = {journal_mode}")

    sync_mode = os.environ.get("LDR_DB_SYNCHRONOUS", "NORMAL")
    cursor_or_conn.execute(f"PRAGMA synchronous = {sync_mode}")


def verify_sqlcipher_connection(cursor_or_conn: Any) -> bool:
    """
    Verify that the SQLCipher connection is working correctly.

    Args:
        cursor_or_conn: SQLCipher cursor or connection object

    Returns:
        True if the connection is valid, False otherwise
    """
    try:
        # Force key derivation with test query
        cursor_or_conn.execute("SELECT 1")
        result = (
            cursor_or_conn.fetchone()
            if hasattr(cursor_or_conn, "fetchone")
            else cursor_or_conn.execute("SELECT 1").fetchone()
        )
        return result == (1,)
    except Exception:
        logger.exception("SQLCipher verification failed")
        return False


def create_sqlcipher_connection(db_path: str, password: str) -> Any:
    """
    Create a properly configured SQLCipher connection.

    Args:
        db_path: Path to the database file
        password: The password for encryption

    Returns:
        SQLCipher connection object

    Raises:
        ImportError: If sqlcipher3 is not available
        ValueError: If the connection cannot be established
    """
    # Import the appropriate SQLCipher module
    from .sqlcipher_compat import get_sqlcipher_module

    try:
        sqlcipher3 = get_sqlcipher_module()
    except ImportError:
        raise ImportError(
            "sqlcipher3 is not available for encrypted databases. "
            "Ensure SQLCipher system library is installed, then run: pdm install"
        )

    conn = sqlcipher3.connect(str(db_path))
    cursor = conn.cursor()

    # Set encryption key
    set_sqlcipher_key(cursor, password)

    # Apply SQLCipher settings
    apply_sqlcipher_pragmas(cursor, creation_mode=False)

    # Verify connection
    if not verify_sqlcipher_connection(cursor):
        conn.close()
        raise ValueError("Failed to establish encrypted database connection")

    # Apply performance settings
    apply_performance_pragmas(cursor)

    cursor.close()
    return conn
