"""SQLAlchemy 2.0 engine + session factory.

We use a single global engine per process. For tests (and SQLite in dev),
StaticPool keeps the same in-memory database across connections; for
Postgres in production we use SQLAlchemy's default pool.
"""

from __future__ import annotations

from collections.abc import Iterator
from contextlib import contextmanager
from functools import lru_cache

from sqlalchemy import create_engine
from sqlalchemy.engine import Engine
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
from sqlalchemy.pool import StaticPool

from ocr_sprint.config import get_settings


class Base(DeclarativeBase):
    """Common SQLAlchemy declarative base."""


@lru_cache(maxsize=1)
def get_engine() -> Engine:
    s = get_settings()
    kwargs: dict[str, object] = {
        "echo": s.database_echo,
        "future": True,
    }
    # SQLite needs special handling: same connection across threads (Celery
    # eager mode + FastAPI) requires `check_same_thread=False`. For the
    # ``sqlite:///:memory:`` URL we also use StaticPool to reuse the same
    # underlying connection so test fixtures see committed data.
    if s.database_url.startswith("sqlite"):
        kwargs["connect_args"] = {"check_same_thread": False}
        if ":memory:" in s.database_url or s.database_url.endswith(":memory:"):
            kwargs["poolclass"] = StaticPool
    return create_engine(s.database_url, **kwargs)


@lru_cache(maxsize=1)
def get_sessionmaker() -> sessionmaker[Session]:
    return sessionmaker(bind=get_engine(), expire_on_commit=False, autoflush=False)


@contextmanager
def session_scope() -> Iterator[Session]:
    """Yield a SQLAlchemy session and commit/rollback at the boundary."""
    factory = get_sessionmaker()
    session = factory()
    try:
        yield session
        session.commit()
    except Exception:
        session.rollback()
        raise
    finally:
        session.close()


def reset_engine_cache() -> None:
    """Clear cached engine + sessionmaker. Used by tests when changing DB URL."""
    get_engine.cache_clear()
    get_sessionmaker.cache_clear()
