import logging
from contextlib import asynccontextmanager
from typing import AsyncGenerator
from urllib.parse import urlparse

from fastapi import Depends, HTTPException, Request
from sqlalchemy import text
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import (
    AsyncEngine,
    AsyncSession,
    async_sessionmaker,
    create_async_engine,
)
from sqlalchemy.pool import AsyncAdaptedQueuePool

from src.config.settings import settings_instance

logger = logging.getLogger(__name__)

# =============================================================================
# DATABASE CONFIGURATION
# =============================================================================

DATABASE_URL = (
    f"postgresql+psycopg://{settings_instance.DB_USER}:"
    f"{settings_instance.DB_PASSWORD}@"
    f"{settings_instance.DB_HOST}:"
    f"{settings_instance.DB_PORT}/"
    f"{settings_instance.DB_NAME}"
)

CONNECT_ARGS = {
    "application_name": "snr-mrm-server",
    "options": "-c statement_timeout=30000 -c idle_in_transaction_session_timeout=30000",
}

# Domain -> (database_name, expiry_monotonic) cache with TTL
_domain_db_cache: dict[str] = {}

# Per-database engine cache — avoids creating a new pool on every request
_engine_cache: dict[str, AsyncEngine] = {}

# =============================================================================
# ASYNC ENGINE (master DB)
# =============================================================================

master_engine = create_async_engine(
    DATABASE_URL,
    poolclass=AsyncAdaptedQueuePool,
    pool_size=10,
    max_overflow=1,
    pool_pre_ping=True,
    pool_recycle=600,
    pool_timeout=10,
    echo=False,
    connect_args=CONNECT_ARGS,
)

# =============================================================================
# SESSION FACTORY
# =============================================================================

master_session_factory = async_sessionmaker(
    bind=master_engine,
    class_=AsyncSession,
    autocommit=False,
    autoflush=False,
    expire_on_commit=False,
)


async def get_master_db() -> AsyncGenerator[AsyncSession, None]:
    """FastAPI dependency for master database sessions."""
    async with master_session_factory() as session:
        try:
            yield session
        except SQLAlchemyError as e:
            await session.rollback()
            logger.error(f"Database session error: {e}")
            raise
        except Exception:
            await session.rollback()
            raise


@asynccontextmanager
async def get_master_db_context() -> AsyncGenerator[AsyncSession, None]:
    """Async context manager for master database sessions."""
    session = master_session_factory()
    try:
        yield session
    except SQLAlchemyError as e:
        await session.rollback()
        logger.error(f"Database error in context: {e}")
        raise
    except Exception as e:
        await session.rollback()
        logger.error(f"Unexpected error in database context: {e}")
        raise
    finally:
        await session.close()


async def get_database_name(
    request: Request, master_db: AsyncSession = Depends(get_master_db)
) -> str:
    """FastAPI dependency: resolve database_name from domain_map via request origin/referer."""
    origin = request.headers.get("origin")
    referer = request.headers.get("referer")
    domain = origin or referer
    if not domain:
        raise HTTPException(status_code=400, detail="Origin or Referer header missing")

    parsed_domain = urlparse(domain).hostname
    if not parsed_domain:
        raise HTTPException(status_code=400, detail="Invalid Origin/Referer header")

    if parsed_domain == "localhost":
        x_origin = request.headers.get("x-origin")
        x_referer = request.headers.get("x-referer")
        domain = x_origin or x_referer
        if not domain:
            raise HTTPException(
                status_code=400,
                detail="X-Origin or X-Referer header missing for localhost",
            )
        parsed_domain = urlparse(domain).hostname
        if not parsed_domain:
            raise HTTPException(
                status_code=400, detail="Invalid X-Origin/X-Referer header"
            )

    # Check TTL cache
    db_name = _domain_db_cache.get(parsed_domain)
    if db_name is not None:
        return db_name

    sql = text(
        "SELECT database_name FROM public.domain_map WHERE domain_url = :domain LIMIT 1"
    )
    result = await master_db.execute(sql, {"domain": parsed_domain})
    row = result.fetchone()

    if not row:
        raise HTTPException(
            status_code=404, detail=f"No domain mapping found for {parsed_domain}"
        )

    _domain_db_cache[parsed_domain] = row[0]
    return row[0]


@asynccontextmanager
async def get_session(database_name: str) -> AsyncGenerator[AsyncSession, None]:
    db_url = (
        f"postgresql+psycopg://{settings_instance.DB_USER}:"
        f"{settings_instance.DB_PASSWORD}@{settings_instance.DB_HOST}:"
        f"{settings_instance.DB_PORT}/{database_name}"
    )
    engine = create_async_engine(
        db_url,
        pool_size=1,
        max_overflow=1,
        pool_pre_ping=True,
        pool_recycle=600,
        echo=False,
    )

    session_factory = async_sessionmaker(
        bind=engine,
        class_=AsyncSession,
        expire_on_commit=False,
        autoflush=False,
        autocommit=False,
    )
    async with session_factory() as session:
        try:
            yield session
        except Exception as e:
            await session.rollback()
            logger.error(f"Unexpected error in database context: {e}")
            raise
        finally:
            await session.close()
            await engine.dispose()
