"""
Gestion des connexions PostgreSQL pour PriceWatch Phase 2.

Fournit:
- Engine SQLAlchemy avec connection pooling
- Session factory avec context manager
- Initialisation des tables
- Health check

Justification technique:
- Connection pooling: réutilisation connexions pour performance
- Context manager: garantit fermeture session (pas de leak)
- pool_pre_ping: vérifie connexion avant usage (robustesse)
- echo=debug: logs SQL en mode debug
"""

from contextlib import contextmanager
from typing import Generator, Optional

from sqlalchemy import create_engine, text
from sqlalchemy.engine import Engine
from sqlalchemy.engine.url import make_url
from sqlalchemy.exc import OperationalError, SQLAlchemyError
from sqlalchemy.orm import Session, sessionmaker

from pricewatch.app.core.config import AppConfig, get_config
from pricewatch.app.core.logging import get_logger
from pricewatch.app.db.models import Base

logger = get_logger("db.connection")

# Global engine instance (singleton)
_engine: Optional[Engine] = None
_session_factory: Optional[sessionmaker] = None


def get_engine(config: Optional[AppConfig] = None) -> Engine:
    """
    Récupère ou crée l'Engine SQLAlchemy (singleton).

    Args:
        config: Configuration app (utilise get_config() si None)

    Returns:
        Engine SQLAlchemy configuré

    Justification:
    - Singleton: une seule pool de connexions par application
    - pool_pre_ping: vérifie connexion avant usage (évite "connection closed")
    - pool_size=5, max_overflow=10: limite connexions (15 max)
    - echo=debug: logs SQL pour debugging
    """
    global _engine

    if _engine is None:
        if config is None:
            config = get_config()

        db_url = config.db.url
        url = make_url(db_url)
        is_sqlite = url.get_backend_name() == "sqlite"

        logger.info(f"Creating database engine: {db_url}")

        engine_kwargs = {
            "pool_pre_ping": True,
            "pool_recycle": 3600,
            "echo": config.debug,
        }

        if not is_sqlite:
            engine_kwargs.update(
                {
                    "pool_size": 5,
                    "max_overflow": 10,
                }
            )

        _engine = create_engine(db_url, **engine_kwargs)

        logger.info("Database engine created successfully")

    return _engine


def init_db(config: Optional[AppConfig] = None) -> None:
    """
    Initialise la base de données (crée toutes les tables).

    Args:
        config: Configuration app (utilise get_config() si None)

    Raises:
        OperationalError: Si connexion impossible
        SQLAlchemyError: Si création tables échoue

    Note:
        Utilise Base.metadata.create_all() - idempotent (ne crash pas si tables existent)
    """
    if config is None:
        config = get_config()

    logger.info("Initializing database...")

    try:
        engine = get_engine(config)

        # Créer toutes les tables définies dans Base.metadata
        Base.metadata.create_all(bind=engine)

        logger.info("Database initialized successfully")
        logger.info(f"Tables created: {', '.join(Base.metadata.tables.keys())}")

    except OperationalError as e:
        logger.error(f"Failed to connect to database: {e}")
        raise
    except SQLAlchemyError as e:
        logger.error(f"Failed to create tables: {e}")
        raise


def get_session_factory(config: Optional[AppConfig] = None) -> sessionmaker:
    """
    Récupère ou crée la session factory (singleton).

    Args:
        config: Configuration app (utilise get_config() si None)

    Returns:
        Session factory SQLAlchemy

    Justification:
    - expire_on_commit=False: objets restent accessibles après commit
    - autocommit=False, autoflush=False: contrôle explicite
    """
    global _session_factory

    if _session_factory is None:
        engine = get_engine(config)

        _session_factory = sessionmaker(
            bind=engine,
            expire_on_commit=False,  # Objets restent accessibles après commit
            autocommit=False,  # Contrôle explicite du commit
            autoflush=False,  # Contrôle explicite du flush
        )

        logger.debug("Session factory created")

    return _session_factory


@contextmanager
def get_session(config: Optional[AppConfig] = None) -> Generator[Session, None, None]:
    """
    Context manager pour session SQLAlchemy.

    Args:
        config: Configuration app (utilise get_config() si None)

    Yields:
        Session SQLAlchemy

    Usage:
        with get_session() as session:
            product = session.query(Product).filter_by(reference="B08N5WRWNW").first()
            session.commit()

    Justification:
    - Context manager: garantit fermeture session (pas de leak)
    - Rollback automatique sur exception
    - Close automatique en fin de bloc
    """
    factory = get_session_factory(config)
    session = factory()

    try:
        logger.debug("Session opened")
        yield session
    except Exception as e:
        logger.error(f"Session error, rolling back: {e}")
        session.rollback()
        raise
    finally:
        logger.debug("Session closed")
        session.close()


def check_db_connection(config: Optional[AppConfig] = None) -> bool:
    """
    Vérifie la connexion à la base de données (health check).

    Args:
        config: Configuration app (utilise get_config() si None)

    Returns:
        True si connexion OK, False sinon

    Note:
        Execute une query simple: SELECT 1
    """
    if config is None:
        config = get_config()

    try:
        engine = get_engine(config)

        with engine.connect() as conn:
            result = conn.execute(text("SELECT 1"))
            result.scalar()

        logger.info("Database connection OK")
        return True

    except OperationalError as e:
        logger.error(f"Database connection failed: {e}")
        return False
    except SQLAlchemyError as e:
        logger.error(f"Database health check failed: {e}")
        return False


def reset_engine() -> None:
    """
    Reset l'engine global (pour tests).

    Note:
        Dispose l'engine et reset les singletons.
    """
    global _engine, _session_factory

    if _engine is not None:
        logger.debug("Disposing database engine")
        _engine.dispose()
        _engine = None

    _session_factory = None
    logger.debug("Engine reset complete")
