from contextlib import contextmanager

from sqlalchemy import create_engine
from sqlalchemy.engine import URL, Engine
from sqlalchemy.orm import DeclarativeBase, sessionmaker

from app.settings import (
    MYSQL_ENABLED,
    APP_STORAGE_BACKEND,
    MYSQL_DATABASE,
    MYSQL_HOST,
    MYSQL_PASSWORD,
    MYSQL_PORT,
    MYSQL_SQL_ECHO,
    MYSQL_USER,
)
from app.tenant import get_database_name


def storage_usa_banco() -> bool:
    """Indica se os lotes OMR são persistidos no MySQL."""
    return MYSQL_ENABLED and APP_STORAGE_BACKEND == "mysql"


def mysql_habilitado() -> bool:
    return MYSQL_ENABLED


def jobs_usam_banco() -> bool:
    # Jobs são operacionais e dependem sempre do MySQL; não há fallback em
    # JSON para evitar criar arquivos dentro de ``processamentos``.
    return True


def kanban_usa_banco() -> bool:
    return MYSQL_ENABLED


def auth_usa_banco() -> bool:
    # Sessões e tokens são exclusivamente MySQL. Sem banco, a autenticação
    # deve falhar explicitamente em vez de gravar ``auth.json``.
    return True


class Base(DeclarativeBase):
    pass


def _montar_database_url(database: str) -> str:
    return URL.create(
        drivername="mysql+pymysql",
        username=MYSQL_USER,
        password=MYSQL_PASSWORD,
        host=MYSQL_HOST,
        port=MYSQL_PORT,
        database=database,
        query={"charset": "utf8mb4"},
    ).render_as_string(hide_password=False)


_ENGINE_CACHE: dict[str, Engine] = {}
_SESSION_FACTORY_CACHE: dict[str, sessionmaker] = {}


def get_engine(database: str | None = None) -> Engine:
    database = str(database or get_database_name() or MYSQL_DATABASE).strip()
    if not database:
        raise RuntimeError("Nome do banco MySQL nao definido.")

    if database not in _ENGINE_CACHE:
        _ENGINE_CACHE[database] = create_engine(
            _montar_database_url(database),
            pool_pre_ping=True,
            pool_recycle=3600,
            pool_size=15,
            max_overflow=30,
            future=True,
            echo=MYSQL_SQL_ECHO,
        )
        _SESSION_FACTORY_CACHE[database] = sessionmaker(
            bind=_ENGINE_CACHE[database],
            autoflush=False,
            autocommit=False,
            expire_on_commit=False,
        )

    return _ENGINE_CACHE[database]


def get_session_factory(database: str | None = None) -> sessionmaker:
    database = str(database or get_database_name() or MYSQL_DATABASE).strip()
    get_engine(database)
    return _SESSION_FACTORY_CACHE[database]


ENGINE = None
SessionLocal = None

if mysql_habilitado():
    ENGINE = get_engine(MYSQL_DATABASE)
    SessionLocal = get_session_factory(MYSQL_DATABASE)


@contextmanager
def get_db_session(database: str | None = None):
    if not mysql_habilitado():
        raise RuntimeError("Banco de dados MySQL nao esta habilitado.")

    factory = get_session_factory(database)
    session = factory()
    try:
        yield session
        session.commit()
    except Exception:
        session.rollback()
        raise
    finally:
        session.close()


def init_db():
    if not mysql_habilitado():
        return

    from app.migrations import run_migrations

    banco = str(MYSQL_DATABASE).strip()
    if banco:
        run_migrations(get_engine(banco))


def _garantir_colunas_kanban():
    if not mysql_habilitado():
        return
    from app.migrations import _migration_003_kanban_cards_columns

    _migration_003_kanban_cards_columns(get_engine())


def _migrar_kanban_colunas_schema():
    if not mysql_habilitado():
        return
    from app.migrations import _migration_004_kanban_colunas_schema

    _migration_004_kanban_colunas_schema(get_engine())


def _garantir_colunas_kanban_etapas():
    if not mysql_habilitado():
        return
    from app.migrations import _migration_005_kanban_etapas_seed

    _migration_005_kanban_etapas_seed(get_engine())
