"""
Authentication database initialization and management.
This manages the central ldr_auth.db which only stores usernames.
"""

import threading
from contextlib import contextmanager
from pathlib import Path
from typing import Optional

from loguru import logger
from sqlalchemy import create_engine, event
from sqlalchemy.engine import Engine
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.pool import QueuePool
from sqlalchemy.schema import CreateIndex, CreateTable

from ..config.paths import get_data_directory
from .models.auth import User
from .pool_config import POOL_PRE_PING, POOL_RECYCLE_SECONDS


# Global cached engine for auth database to prevent file descriptor leaks
_auth_engine: Optional[Engine] = None
_auth_engine_path: Optional[Path] = (
    None  # Track the path the engine was created for
)
_auth_engine_lock = threading.Lock()


def get_auth_db_path() -> Path:
    """Get the path to the authentication database."""
    return get_data_directory() / "ldr_auth.db"


def _get_auth_engine() -> Engine:
    """
    Get or create a cached engine for the auth database.

    This prevents file descriptor leaks by reusing a single engine
    instead of creating a new one for every session.

    The engine is invalidated if the data directory path changes
    (e.g., during testing when LDR_DATA_DIR is set to a temp directory).
    """
    global _auth_engine, _auth_engine_path

    auth_db_path = get_auth_db_path()

    # Check if we have a cached engine for the current path
    if _auth_engine is not None and _auth_engine_path == auth_db_path:
        return _auth_engine

    with _auth_engine_lock:
        # Double-check after acquiring lock
        if _auth_engine is not None and _auth_engine_path == auth_db_path:
            return _auth_engine

        # If path changed, dispose old engine first
        if _auth_engine is not None and _auth_engine_path != auth_db_path:
            try:
                _auth_engine.dispose()
                logger.debug(
                    "Disposed auth engine due to data directory change"
                )
            except Exception:
                logger.warning("Error disposing old auth engine")
            _auth_engine = None
            _auth_engine_path = None

        # Ensure database exists
        if not auth_db_path.exists():
            init_auth_database()

        # Create engine with connection pooling
        _auth_engine = create_engine(
            f"sqlite:///{auth_db_path}",
            poolclass=QueuePool,
            pool_size=5,
            max_overflow=10,
            pool_pre_ping=POOL_PRE_PING,
            pool_recycle=POOL_RECYCLE_SECONDS,
            echo=False,
        )

        def _apply_auth_pragmas(dbapi_connection, connection_record):
            dbapi_connection.execute("PRAGMA busy_timeout = 10000")
            dbapi_connection.execute("PRAGMA temp_store = MEMORY")

        event.listen(_auth_engine, "connect", _apply_auth_pragmas)

        _auth_engine_path = auth_db_path
        logger.debug("Created cached auth database engine")

        return _auth_engine


def init_auth_database():
    """Initialize the authentication database if it doesn't exist.

    Uses SQL-level IF NOT EXISTS (via CreateTable/CreateIndex) so that
    concurrent calls are handled atomically by the database engine,
    eliminating the TOCTOU race in SQLAlchemy's checkfirst inspect.
    """
    auth_db_path = get_auth_db_path()

    # Ensure the data directory exists
    auth_db_path.parent.mkdir(parents=True, exist_ok=True)

    logger.debug(f"Ensuring auth database at {auth_db_path}")

    # Create the database with a temporary engine
    engine = create_engine(f"sqlite:///{auth_db_path}")

    # Use SQL-level IF NOT EXISTS — idempotent and atomic, safe for
    # concurrent calls without Python-level locking.
    with engine.begin() as conn:
        conn.execute(CreateTable(User.__table__, if_not_exists=True))
        for index in User.__table__.indexes:
            conn.execute(CreateIndex(index, if_not_exists=True))

    # Dispose the temporary engine
    engine.dispose()

    logger.debug("Auth database initialized successfully")


def get_auth_db_session() -> Session:
    """
    Get a session for the auth database.

    IMPORTANT: The caller MUST close the session when done to return
    the connection to the pool. Use auth_db_session() context manager
    for automatic cleanup.
    """
    engine = _get_auth_engine()
    SessionFactory = sessionmaker(bind=engine)
    return SessionFactory()


@contextmanager
def auth_db_session():
    """
    Context manager for auth database sessions.

    Usage:
        with auth_db_session() as session:
            user = session.query(User).filter_by(username=username).first()

    The session is automatically closed when the context exits.
    """
    session = get_auth_db_session()
    try:
        yield session
    finally:
        from ..utilities.resource_utils import safe_close

        safe_close(session, "auth DB session")


def dispose_auth_engine():
    """
    Dispose the auth engine (for shutdown/cleanup).
    """
    global _auth_engine, _auth_engine_path

    with _auth_engine_lock:
        if _auth_engine is not None:
            _auth_engine.dispose()
            _auth_engine = None
            _auth_engine_path = None
            logger.debug("Disposed auth database engine")
