# config/db.py
from __future__ import annotations
import os

from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker, scoped_session

from config.base import Config
from config.db_router import resolve_db_name

Base = declarative_base()

_POOL_SIZE = int(os.getenv("DB_POOL_SIZE", "5"))
_MAX_OVERFLOW = int(os.getenv("DB_MAX_OVERFLOW", "10"))
_ENGINE_CACHE: dict[str, object] = {}
_SESSION_FACTORY_CACHE: dict[str, sessionmaker] = {}


def build_database_url(db_name: str) -> str:
    return (
        f"{Config.DB_ENGINE}://{Config.DB_USER}:{Config.DB_PASSWORD}"
        f"@{Config.DB_HOST}:{Config.DB_PORT}/{db_name}"
    )


def get_engine(db_name: str | None = None):
    # DB_NAME es un fallback transicional para rutas que aún no envían contexto tenant.
    resolved_db_name = db_name or Config.DB_NAME
    engine = _ENGINE_CACHE.get(resolved_db_name)
    if engine is None:
        engine = create_engine(
            build_database_url(resolved_db_name),
            pool_pre_ping=True,
            pool_recycle=1800,
            pool_size=_POOL_SIZE,
            max_overflow=_MAX_OVERFLOW,
            future=True,
        )
        _ENGINE_CACHE[resolved_db_name] = engine
    return engine


def get_session_factory(db_name: str | None = None) -> sessionmaker:
    resolved_db_name = db_name or Config.DB_NAME
    factory = _SESSION_FACTORY_CACHE.get(resolved_db_name)
    if factory is None:
        factory = sessionmaker(
            bind=get_engine(resolved_db_name),
            autocommit=False,
            autoflush=False,
            future=True,
        )
        _SESSION_FACTORY_CACHE[resolved_db_name] = factory
    return factory


# Binding por defecto transicional: no representa la DB principal del sistema,
# solo el tenant fallback mientras el routing multitenant termina de adoptarse.
engine = get_engine(Config.DB_NAME)
SessionLocal = scoped_session(get_session_factory(Config.DB_NAME))
DATABASE_URL = build_database_url(Config.DB_NAME)


def get_session(
    hostname: str | None = None,
    routing_key: str | None = None,
    db_name: str | None = None,
):
    """Devuelve una Session; sin contexto tenant cae al DB_* transicional."""
    if db_name:
        return get_session_factory(db_name)()
    if hostname or routing_key:
        resolved_db_name = resolve_db_name(hostname=hostname, routing_key=routing_key)
        if resolved_db_name == Config.DB_NAME:
            return SessionLocal()
        return get_session_factory(resolved_db_name)()
    return SessionLocal()


def init_db_app(app):
    """
    Adjunta una fábrica de sesión a la app Flask y configura teardown.

    Luego en cualquier lugar con contexto de app se puede usar:
        from flask import current_app
        session = current_app.session()
    """
    app.session = SessionLocal  # podrás usar current_app.session()

    @app.teardown_appcontext
    def remove_session(exc=None):
        # Cierra/remueve la sesión del contexto actual (scoped_session)
        SessionLocal.remove()

    return app
