"""
{{ base_module }}/postgres.py

Production-ready PostgreSQL integration for FastAPI.
Provides async/sync SQLAlchemy engines, session management,
connection pooling, transactions, and health checks.

Generated by RapidKit - Database PostgreSQL Module
"""

from typing import AsyncGenerator, Generator, Optional
from contextlib import asynccontextmanager, contextmanager

from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker, Session, declarative_base
from sqlalchemy.ext.asyncio import (
    AsyncSession,
    create_async_engine,
    async_sessionmaker,
)
from sqlalchemy.exc import SQLAlchemyError
import os
from types import SimpleNamespace

try:  # Prefer canonical settings module when installed
    from modules.free.essentials.settings.settings import settings
except ImportError:  # pragma: no cover
    settings = None  # type: ignore


def _parse_bool(raw: str | None, default: bool) -> bool:
    if raw is None:
        return default
    value = raw.strip().lower()
    if value in {"1", "true", "yes", "on"}:
        return True
    if value in {"0", "false", "no", "off"}:
        return False
    return default


def _parse_int(raw: str | None, default: int) -> int:
    if raw is None:
        return default
    try:
        return int(raw)
    except ValueError:
        return default


if settings is None:  # pragma: no cover - exercised when essentials/settings isn't installed
    settings = SimpleNamespace(
        DATABASE_URL=os.getenv(
            "DATABASE_URL",
            "postgresql://postgres:postgres@localhost:5432/app",
        ),
        TEST_DATABASE_URL=os.getenv("TEST_DATABASE_URL"),
        DB_ECHO=_parse_bool(os.getenv("DB_ECHO"), False),
        DB_POOL_SIZE=_parse_int(os.getenv("DB_POOL_SIZE"), 10),
        DB_MAX_OVERFLOW=_parse_int(os.getenv("DB_MAX_OVERFLOW"), 20),
        DB_POOL_RECYCLE=_parse_int(os.getenv("DB_POOL_RECYCLE"), 3600),
        DB_POOL_TIMEOUT=_parse_int(os.getenv("DB_POOL_TIMEOUT"), 30),
    )

try:  # Prefer canonical logging module when installed
    from modules.free.essentials.logging.logging import get_logger
except ImportError:  # pragma: no cover
    import logging

    def get_logger(name: str):  # type: ignore[override]
        return logging.getLogger(name)

# ========== SQLAlchemy Base ==========
Base = declarative_base()

# ========== Logging ==========
logger = get_logger(__name__)

# ========== Engine Configuration ==========
engine_config = {
    "echo": settings.DB_ECHO,
    "pool_size": settings.DB_POOL_SIZE,
    "max_overflow": settings.DB_MAX_OVERFLOW,
    "pool_recycle": settings.DB_POOL_RECYCLE,
    "pool_timeout": settings.DB_POOL_TIMEOUT,
}

# ========== Async Engine ==========
async_engine = create_async_engine(
    settings.DATABASE_URL.replace("postgresql://", "postgresql+asyncpg://"),
    future=True,
    **engine_config,
)

# ========== Sync Engine ==========
sync_engine = create_engine(
    settings.DATABASE_URL.replace("postgresql://", "postgresql+psycopg://"),
    future=True,
    **engine_config,
)

# ========== Test Engines (Optional) ==========
test_database_url = (
    getattr(settings, "TEST_DATABASE_URL", None)
    or getattr(settings, "test_database_url", None)
)

if test_database_url:
    test_async_engine = create_async_engine(
        test_database_url.replace("postgresql://", "postgresql+asyncpg://"),
        future=True,
        **engine_config,
    )
    test_sync_engine = create_engine(
        test_database_url.replace("postgresql://", "postgresql+psycopg://"),
        future=True,
        **engine_config,
    )
else:
    test_async_engine = None
    test_sync_engine = None

# ========== Session Makers ==========
AsyncSessionLocal = async_sessionmaker(
    bind=async_engine,
    expire_on_commit=False,
    class_=AsyncSession,
)

SyncSessionLocal = sessionmaker(
    bind=sync_engine,
    autocommit=False,
    autoflush=False,
)

# ========== Test Session Makers (Optional) ==========
TestAsyncSessionLocal = (
    async_sessionmaker(
        bind=test_async_engine,
        expire_on_commit=False,
        class_=AsyncSession,
    )
    if test_database_url and test_async_engine
    else None
)

TestSyncSessionLocal = (
    sessionmaker(
        bind=test_sync_engine,
        autocommit=False,
        autoflush=False,
    )
    if test_database_url and test_sync_engine
    else None
)


# ========== FastAPI Dependency Injection ==========
async def get_postgres_db() -> AsyncGenerator[AsyncSession, None]:
    """
    FastAPI dependency for async PostgreSQL database session.

    Usage:
        @app.get("/users")
        async def get_users(db: AsyncSession = Depends(get_postgres_db)):
            result = await db.execute(select(User))
            return result.scalars().all()

    Yields:
        AsyncSession: Async SQLAlchemy session
    """
    async with AsyncSessionLocal() as session:
        try:
            yield session
        finally:
            await session.close()


def get_sync_db() -> Generator[Session, None, None]:
    """
    Dependency for sync PostgreSQL database session.

    Usage:
        @app.get("/legacy")
        def get_legacy_data(db: Session = Depends(get_sync_db)):
            return db.execute(text("SELECT * FROM legacy_table")).all()

    Yields:
        Session: Sync SQLAlchemy session
    """
    session = SyncSessionLocal()
    try:
        yield session
    finally:
        session.close()


# ========== Transaction Context Managers ==========
@asynccontextmanager
async def transactional_async() -> AsyncGenerator[AsyncSession, None]:
    """
    Async transaction context manager with automatic commit/rollback.

    Usage:
        async with transactional_async() as session:
            user = User(name="John")
            session.add(user)
            # Automatically commits on success, rolls back on error

    Yields:
        AsyncSession: Transactional async session
    """
    async with AsyncSessionLocal() as session:
        async with session.begin():
            try:
                yield session
            except Exception:
                await session.rollback()
                raise


@contextmanager
def transactional_sync() -> Generator[Session, None, None]:
    """
    Sync transaction context manager with automatic commit/rollback.

    Usage:
        with transactional_sync() as session:
            user = User(name="John")
            session.add(user)
            # Automatically commits on success, rolls back on error

    Yields:
        Session: Transactional sync session
    """
    session = SyncSessionLocal()
    try:
        with session.begin():
            try:
                yield session
            except Exception:
                session.rollback()
                raise
    finally:
        session.close()


# ========== Health Checks ==========
async def check_postgres_connection() -> None:
    """
    Check async PostgreSQL connection health.

    Raises:
        HTTPException: If connection fails
    """
    try:
        async with async_engine.begin() as conn:
            await conn.execute(text("SELECT 1"))
        logger.info("✅ PostgreSQL async connection is active")
    except SQLAlchemyError as e:
        logger.error(f"❌ Async DB connection failed: {e}")
        raise HTTPException(status_code=500, detail="Async DB connection failed")


def check_postgres_connection_sync() -> None:
    """
    Check sync PostgreSQL connection health.

    Raises:
        HTTPException: If connection fails
    """
    try:
        with sync_engine.connect() as conn:
            conn.execute(text("SELECT 1"))
        logger.info("✅ PostgreSQL sync connection is active")
    except SQLAlchemyError as e:
        logger.error(f"❌ Sync DB connection failed: {e}")
        raise HTTPException(status_code=500, detail="Sync DB connection failed")


async def get_pool_status() -> dict:
    """
    Get current connection pool status.

    Returns:
        Dict containing pool statistics
    """
    pool = async_engine.pool
    return {
        "pool_size": pool.size(),
        "checked_in": pool.checkedin(),
        "checked_out": pool.checkedout(),
        "overflow": pool.overflow(),
        "total_connections": pool.size() + pool.overflow(),
    }


# ========== Lifecycle Management ==========
async def close_async_engine() -> None:
    """
    Close async engine and dispose of connection pool.
    Call this during application shutdown.
    """
    await async_engine.dispose()
    logger.info("🧹 Async PostgreSQL engine disposed")


def close_sync_engine() -> None:
    """
    Close sync engine and dispose of connection pool.
    Call this during application shutdown.
    """
    sync_engine.dispose()
    logger.info("🧹 Sync PostgreSQL engine disposed")


async def initialize_database() -> None:
    """
    Initialize database (create tables, etc.).
    Call this during application startup if needed.
    """
    async with async_engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    logger.info("✅ Database initialized")


# ========== Utilities ==========
async def execute_raw_sql(sql: str) -> list:
    """
    Execute raw SQL query (use with caution).

    Args:
        sql: SQL query string

    Returns:
        List of results
    """
    async with AsyncSessionLocal() as session:
        result = await session.execute(text(sql))
        return result.fetchall()


def get_database_url(hide_password: bool = True) -> str:
    """
    Get database URL with optional password masking.

    Args:
        hide_password: Whether to mask password in URL

    Returns:
        Database URL string
    """
    url = settings.DATABASE_URL
    if hide_password and "@" in url:
        parts = url.split("@")
        credentials = parts[0].split("://")[1]
        if ":" in credentials:
            user = credentials.split(":")[0]
            url = url.replace(credentials, f"{user}:***")
    return url
