"""Database session management and initialization module."""

from contextlib import contextmanager
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
from urllib.parse import urlparse

from alembic.config import Config
from alembic import command
import os

from lecrapaud.config import DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME, DB_URI

_engine = None
_SessionLocal = None


def _normalize_db_uri(uri: str) -> str:
    """Normalize the URI to include the correct driver."""
    if "postgresql" in uri or "postgres://" in uri:
        if uri.startswith("postgres://"):
            uri = uri.replace("postgres://", "postgresql://", 1)
        if "postgresql://" in uri and "+psycopg2" not in uri:
            uri = uri.replace("postgresql://", "postgresql+psycopg2://", 1)
        return uri
    # MySQL by default
    if "mysql://" in uri and "pymysql://" not in uri:
        uri = uri.replace("mysql://", "mysql+pymysql://")
    return uri


def _is_postgresql(uri: str) -> bool:
    return uri is not None and ("postgresql" in uri or "postgres" in uri)


if DB_URI:
    DATABASE_URL = _normalize_db_uri(DB_URI)
elif DB_USER:
    DATABASE_URL = (
        f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
    )
else:
    DATABASE_URL = None


def init_db(uri: str = None):
    global _engine, _SessionLocal, DATABASE_URL, DB_URI
    if _SessionLocal is not None:
        return

    # Step 0: Set database URI
    if uri:
        DATABASE_URL = _normalize_db_uri(uri)
    elif DATABASE_URL:
        pass
    else:
        raise ValueError(
            "No database configuration found, please set env variables "
            "DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME or DB_URI, "
            "or provide a `uri` argument to LeCrapaud"
        )
    print(f"Initializing database with URI: {DATABASE_URL}")

    # Step 1: Create root engine without database
    parsed = urlparse(DATABASE_URL)
    db_name = parsed.path.lstrip("/")  # remove leading slash
    is_pg = _is_postgresql(DATABASE_URL)

    # Step 2: Create database if it doesn't exist
    if is_pg:
        # PostgreSQL: try connecting to the 'postgres' maintenance database.
        # On managed hosts (e.g. Clever Cloud), access to the 'postgres' DB
        # may be restricted — fall back to assuming the target DB already exists.
        try:
            root_uri = f"{parsed.scheme}://{parsed.netloc}/postgres"
            root_engine = create_engine(root_uri, isolation_level="AUTOCOMMIT")
            with root_engine.connect() as conn:
                exists = conn.execute(
                    text("SELECT 1 FROM pg_database WHERE datname = :db_name"),
                    {"db_name": db_name},
                ).scalar()
                if not exists:
                    conn.execute(text(f'CREATE DATABASE "{db_name}"'))
            root_engine.dispose()
        except Exception:
            # Cannot access 'postgres' DB — assume target database already exists
            pass
    else:
        # MySQL
        root_uri = f"{parsed.scheme}://{parsed.netloc}"
        root_engine = create_engine(root_uri)
        with root_engine.connect() as conn:
            conn.execute(text(f"CREATE DATABASE IF NOT EXISTS `{db_name}`"))
            conn.commit()

    # Step 3: Configure main engine with connection pooling and reconnection settings
    if is_pg:
        connect_args = {
            "connect_timeout": 10,
            "options": "-c statement_timeout=300000",  # 5 min statement timeout
        }
    else:
        connect_args = {
            "connect_timeout": 10,  # 10 second connection timeout
            "read_timeout": 300,  # 5 min for large SELECT (eager loading, blob reads)
            "write_timeout": 300,  # 5 min for large INSERT (blob uploads)
        }

    _engine = create_engine(
        DATABASE_URL,
        echo=False,
        pool_pre_ping=True,  # Check connection health before using
        pool_recycle=3600,  # Recycle connections after 1 hour
        pool_timeout=30,  # Wait 30 seconds for a connection from the pool
        pool_size=10,  # Maintain up to 10 connections
        max_overflow=10,  # Allow up to 10 more connections during spikes
        connect_args=connect_args,
    )

    # Step 4: Configure session factory
    _SessionLocal = sessionmaker(
        autocommit=False,
        autoflush=False,
        bind=_engine,
        expire_on_commit=False,  # Prevent detached instance errors
    )

    # Step 5: Apply Alembic migrations programmatically
    current_dir = os.path.dirname(__file__)  # → lecrapaud/db
    alembic_ini_path = os.path.join(current_dir, "alembic.ini")
    alembic_dir = os.path.join(
        current_dir, "alembic"
    )  # Use absolute path to alembic directory

    alembic_cfg = Config(alembic_ini_path)
    alembic_cfg.set_main_option("script_location", alembic_dir)  # Use absolute path
    alembic_cfg.set_main_option("sqlalchemy.url", DATABASE_URL)

    command.upgrade(alembic_cfg, "head")


# Dependency to get a session instance
@contextmanager
def get_db():
    """Get a database session with automatic connection management.

    Yields:
        Session: A SQLAlchemy session instance

    The session is automatically committed if no exceptions occur, and rolled back otherwise.
    The connection is automatically returned to the pool when the session is closed.
    """
    if _SessionLocal is None:
        init_db()

    db = _SessionLocal()
    try:
        yield db
    except Exception as e:
        db.rollback()
        raise Exception(e) from e
    finally:
        db.close()
