"""SQLAlchemy engine and session factory.

Engine and session factory are cached globally per process. Only one DATABASE_URL
is effectively used for the process lifetime. Using a different URL later (e.g. in
tests with in-memory SQLite) would still use the first engine. To use a different
URL in tests, set env (e.g. DATABASE_URL) before the first import of this module, or
clear _engine and _SessionLocal in test fixtures. Prefer session_scope() for all
callers so sessions are always closed and rolled back on error.
"""

from contextlib import contextmanager
from typing import Generator

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


_engine = None
_SessionLocal = None


@contextmanager
def session_scope(database_url: str) -> Generator[Session, None, None]:
    """Context manager that yields a session; rolls back on exception, closes on exit.

    Args:
        database_url: SQLAlchemy database URL.

    Yields:
        Session instance. Caller must not use it after exit.
    """
    session = get_session(database_url)
    try:
        yield session
    except Exception:
        session.rollback()
        raise
    finally:
        session.close()


def get_engine(database_url: str):
    """Return cached SQLAlchemy engine for the given URL (one per process)."""
    global _engine
    if _engine is None:
        _engine = create_engine(
            database_url,
            connect_args={"check_same_thread": False}
            if "sqlite" in database_url
            else {},
            echo=False,
        )
    return _engine


def get_session_factory(database_url: str) -> sessionmaker[Session]:
    """Return cached session factory for the given URL (one per process)."""
    global _SessionLocal
    if _SessionLocal is None:
        engine = get_engine(database_url)
        _SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
    return _SessionLocal


def get_session(database_url: str) -> Session:
    """Create a new session from the factory for the given URL."""
    return get_session_factory(database_url)()
