"""Database connection management with connection pooling and error handling.

This module provides database connection management with proper connection pooling,
retry logic, and error handling as specified in the project requirements.
"""

import logging
from contextlib import contextmanager
from typing import Generator, Optional
from urllib.parse import urlparse

from sqlalchemy import create_engine, event, text
from sqlalchemy.engine import Engine
from sqlalchemy.exc import DisconnectionError, OperationalError
from sqlalchemy.orm import Session, sessionmaker
from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_exponential

from ..config import config

logger = logging.getLogger(__name__)

# Global engine and session factory
_engine: Optional[Engine] = None
_SessionLocal: Optional[sessionmaker] = None


def get_database_url() -> str:
    """Get the database URL from configuration.

    Returns:
        Database connection URL

    Raises:
        ValueError: If DATABASE_URL is not configured

    """
    if not config.DATABASE_URL:
        raise ValueError("DATABASE_URL not configured")
    return config.DATABASE_URL


def create_database_engine() -> Engine:
    """Create and configure the database engine with connection pooling.

    Returns:
        Configured SQLAlchemy engine

    Raises:
        ValueError: If database URL is invalid

    """
    global _engine

    if _engine is not None:
        return _engine

    database_url = get_database_url()

    # Parse URL to validate it
    try:
        parsed = urlparse(database_url)
        if not parsed.scheme or not parsed.hostname:
            raise ValueError(f"Invalid database URL: {database_url}")
    except Exception as e:
        raise ValueError(f"Invalid database URL {database_url}: {e}")

    # Engine configuration with connection pooling
    engine_config = {
        "pool_size": config.DATABASE_POOL_SIZE,
        "max_overflow": config.DATABASE_MAX_OVERFLOW,
        "pool_timeout": config.DATABASE_POOL_TIMEOUT,
        "pool_recycle": config.DATABASE_POOL_RECYCLE,
        "pool_pre_ping": True,  # Verify connections before use
        "echo": False,  # Set to True for SQL logging
    }

    # Create engine
    _engine = create_engine(database_url, **engine_config)

    # Add event listeners for connection management
    @event.listens_for(_engine, "connect")
    def set_sqlite_pragma(dbapi_connection, connection_record):
        """Set SQLite pragmas for better performance (if using SQLite)."""
        if "sqlite" in database_url:
            cursor = dbapi_connection.cursor()
            cursor.execute("PRAGMA journal_mode=WAL")
            cursor.execute("PRAGMA synchronous=NORMAL")
            cursor.execute("PRAGMA cache_size=10000")
            cursor.execute("PRAGMA temp_store=MEMORY")
            cursor.close()

    @event.listens_for(_engine, "checkout")
    def receive_checkout(dbapi_connection, connection_record, connection_proxy):
        """Log connection checkout for debugging."""
        logger.debug("Database connection checked out")

    @event.listens_for(_engine, "checkin")
    def receive_checkin(dbapi_connection, connection_record):
        """Log connection checkin for debugging."""
        logger.debug("Database connection checked in")

    logger.info(f"Database engine created with pool_size={config.DATABASE_POOL_SIZE}")
    return _engine


def get_engine() -> Engine:
    """Get the database engine, creating it if necessary.

    Returns:
        Database engine

    """
    if _engine is None:
        return create_database_engine()
    return _engine


def create_session_factory() -> sessionmaker:
    """Create the session factory.

    Returns:
        Configured session factory

    """
    global _SessionLocal

    if _SessionLocal is not None:
        return _SessionLocal

    engine = get_engine()
    _SessionLocal = sessionmaker(
        autocommit=False,
        autoflush=False,
        bind=engine,
        expire_on_commit=False,  # Keep objects accessible after commit
    )

    logger.info("Session factory created")
    return _SessionLocal


def get_session_factory() -> sessionmaker:
    """Get the session factory, creating it if necessary.

    Returns:
        Session factory

    """
    if _SessionLocal is None:
        return create_session_factory()
    return _SessionLocal


@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=4, max=10),
    retry=retry_if_exception_type((OperationalError, DisconnectionError)),
    reraise=True,
)
def get_session() -> Session:
    """Get a database session with retry logic.

    Returns:
        Database session

    Raises:
        SQLAlchemyError: If unable to create session after retries

    """
    session_factory = get_session_factory()
    return session_factory()


@contextmanager
def get_db_session() -> Generator[Session, None, None]:
    """Context manager for database sessions with automatic cleanup.

    Yields:
        Database session

    Example:
        with get_db_session() as session:
            result = session.query(MediaFile).all()

    """
    session = get_session()
    try:
        yield session
        session.commit()
    except Exception:
        session.rollback()
        raise
    finally:
        session.close()


def test_connection() -> bool:
    """Test database connection.

    Returns:
        True if connection successful, False otherwise

    """
    try:
        with get_db_session() as session:
            session.execute(text("SELECT 1"))
            logger.info("Database connection test successful")
            return True
    except Exception as e:
        logger.error(f"Database connection test failed: {e}")
        return False


def close_connections():
    """Close all database connections."""
    global _engine, _SessionLocal

    if _engine:
        _engine.dispose()
        _engine = None
        logger.info("Database engine disposed")

    _SessionLocal = None


def get_connection_info() -> dict:
    """Get database connection information.

    Returns:
        Dictionary with connection information

    """
    database_url = get_database_url()
    parsed = urlparse(database_url)

    return {
        "scheme": parsed.scheme,
        "host": parsed.hostname,
        "port": parsed.port,
        "database": parsed.path.lstrip("/"),
        "pool_size": config.DATABASE_POOL_SIZE,
        "max_overflow": config.DATABASE_MAX_OVERFLOW,
        "pool_timeout": config.DATABASE_POOL_TIMEOUT,
        "pool_recycle": config.DATABASE_POOL_RECYCLE,
    }
