"""Engine, session factory, and the FastAPI session dependency.

The engine is built LAZILY, on first use. Creating it at import time would mean
importing the app requires a reachable DATABASE_URL — which breaks the Docker
build's schema stage, where `app.main` is imported purely to dump the OpenAPI
document and no database exists yet. Lazy construction keeps "import the app"
and "talk to the database" separate concerns.

Schema changes go through Alembic only — never a hand-written ALTER and never
create_all() at startup (react.md).
"""

from __future__ import annotations

from collections.abc import Iterator

from sqlalchemy import Engine, create_engine
from sqlalchemy.orm import Session, sessionmaker

from .config import get_settings, is_configured

_engine: Engine | None = None
_session_factory: sessionmaker[Session] | None = None


def get_engine() -> Engine:
    global _engine
    if _engine is None:
        url = get_settings().database_url
        if not is_configured(url):
            raise RuntimeError(
                "DATABASE_URL is not configured — check .app.env is mounted "
                "and its placeholder was replaced"
            )
        _engine = create_engine(
            url,
            # The app outlives DB restarts; drop connections that died with it.
            pool_pre_ping=True,
            future=True,
        )
    return _engine


def get_session_factory() -> sessionmaker[Session]:
    global _session_factory
    if _session_factory is None:
        _session_factory = sessionmaker(
            bind=get_engine(), autoflush=False, expire_on_commit=False
        )
    return _session_factory


def get_db() -> Iterator[Session]:
    """Request-scoped session. Rolls back on an unhandled exception so a failed
    request can never leave a half-applied transaction behind."""
    db = get_session_factory()()
    try:
        yield db
    except Exception:
        db.rollback()
        raise
    finally:
        db.close()
