"""
Base module for database operations.
Production-ready, type-safe database layer using SQLAlchemy 2.0+
with sync/async support, connection pooling, and modern Python patterns.
"""

from __future__ import annotations

import contextlib
import logging
from contextlib import asynccontextmanager, contextmanager
from typing import Any, AsyncIterator, Generator, Generic, Iterator, Sequence, TypeVar

from sqlalchemy import (
    URL,
    Result,
    Select,
    Update,
    Delete,
    create_engine,
    inspect,
    select,
    update,
    delete,
    text,
)
from sqlalchemy.engine import make_url
from sqlalchemy.ext.asyncio import (
    AsyncEngine,
    AsyncSession,
    async_sessionmaker,
    create_async_engine,
)
from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass, Session, sessionmaker
from sqlalchemy.pool import QueuePool

logger = logging.getLogger(__name__)

T = TypeVar("T", bound="Base")


class Base(DeclarativeBase, MappedAsDataclass):
    """Declarative base with dataclass support for models."""

    pass


class DatabaseConfig:
    """Configuration for database connections."""

    def __init__(
        self,
        host: str = "localhost",
        port: int = 5432,
        database: str = "postgres",
        username: str = "postgres",
        password: str = "password",
        *,
        url: str | None = None,
        pool_size: int = 5,
        max_overflow: int = 10,
        pool_timeout: int = 30,
        pool_recycle: int = 3600,
        pool_pre_ping: bool = True,
        echo: bool = False,
        echo_pool: bool = False,
    ):
        if url is not None:
            parsed = make_url(url)
            self.url = parsed
            self.async_url = parsed.set(drivername="postgresql+asyncpg")
        else:
            self.url = URL.create(
                drivername="postgresql+psycopg",
                username=username,
                password=password,
                host=host,
                port=port,
                database=database,
            )
            self.async_url = URL.create(
                drivername="postgresql+asyncpg",
                username=username,
                password=password,
                host=host,
                port=port,
                database=database,
            )
        self.pool_size = pool_size
        self.max_overflow = max_overflow
        self.pool_timeout = pool_timeout
        self.pool_recycle = pool_recycle
        self.pool_pre_ping = pool_pre_ping
        self.echo = echo
        self.echo_pool = echo_pool


# Default config and engine from DatabaseConfig (host/port/db/user/pass or url)
_config = DatabaseConfig(pool_pre_ping=True, echo=False)
engine = create_engine(
    _config.url,
    poolclass=QueuePool,
    pool_size=_config.pool_size,
    max_overflow=_config.max_overflow,
    pool_timeout=_config.pool_timeout,
    pool_recycle=_config.pool_recycle,
    pool_pre_ping=_config.pool_pre_ping,
    echo=_config.echo,
    echo_pool=_config.echo_pool,
)
SessionLocal = sessionmaker(
    bind=engine,
    autocommit=False,
    autoflush=False,
    expire_on_commit=False,
)


@contextlib.contextmanager
def get_db_session() -> Generator[Session, None, None]:
    """Context manager for database sessions."""
    session = SessionLocal()
    try:
        yield session
        session.commit()
    except Exception as e:
        session.rollback()
        logger.error(f"Session error: {e}", exc_info=True)
        raise
    finally:
        session.close()


class DatabaseManager(Generic[T]):
    """Synchronous database manager with type safety and connection pooling."""

    def __init__(self, config: DatabaseConfig | None = None):
        self.config = config or _config
        self.engine = create_engine(
            self.config.url,
            poolclass=QueuePool,
            pool_size=self.config.pool_size,
            max_overflow=self.config.max_overflow,
            pool_timeout=self.config.pool_timeout,
            pool_recycle=self.config.pool_recycle,
            pool_pre_ping=self.config.pool_pre_ping,
            echo=self.config.echo,
            echo_pool=self.config.echo_pool,
        )
        self.session_factory = sessionmaker(
            bind=self.engine,
            autocommit=False,
            autoflush=False,
            expire_on_commit=False,
        )

    @contextmanager
    def session(self) -> Iterator[Session]:
        session = self.session_factory()
        try:
            yield session
            session.commit()
        except Exception as e:
            session.rollback()
            logger.error(f"Session error: {e}", exc_info=True)
            raise
        finally:
            session.close()

    def create(self, instance: T) -> T:
        with self.session() as session:
            session.add(instance)
            session.flush()
            session.refresh(instance)
            return instance

    def create_many(self, instances: Sequence[T]) -> Sequence[T]:
        with self.session() as session:
            session.add_all(instances)
            session.flush()
            for instance in instances:
                session.refresh(instance)
            return instances

    def get_by_id(self, model: type[T], id_: Any) -> T | None:
        with self.session() as session:
            return session.get(model, id_)

    def get_all(
        self,
        model: type[T],
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        with self.session() as session:
            stmt = select(model)
            if order_by is not None:
                stmt = stmt.order_by(order_by)
            if offset:
                stmt = stmt.offset(offset)
            if limit:
                stmt = stmt.limit(limit)
            return session.execute(stmt).scalars().all()

    def find(
        self,
        model: type[T],
        *,
        filters: dict[str, Any] | None = None,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        with self.session() as session:
            stmt = select(model)
            if filters:
                for key, value in filters.items():
                    stmt = stmt.where(getattr(model, key) == value)
            if order_by is not None:
                stmt = stmt.order_by(order_by)
            if offset:
                stmt = stmt.offset(offset)
            if limit:
                stmt = stmt.limit(limit)
            return session.execute(stmt).scalars().all()

    def find_one(self, model: type[T], *, filters: dict[str, Any]) -> T | None:
        with self.session() as session:
            stmt = select(model)
            for key, value in filters.items():
                stmt = stmt.where(getattr(model, key) == value)
            return session.execute(stmt).scalar_one_or_none()

    def count(self, model: type[T], *, filters: dict[str, Any] | None = None) -> int:
        with self.session() as session:
            stmt = select(model)
            if filters:
                for key, value in filters.items():
                    stmt = stmt.where(getattr(model, key) == value)
            return len(session.execute(stmt).scalars().all())

    def exists(self, model: type[T], *, filters: dict[str, Any]) -> bool:
        return self.find_one(model, filters=filters) is not None

    def find_by_date(
        self,
        model: type[T],
        date_column: str,
        target_date: Any,
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        from datetime import datetime

        from sqlalchemy import Date, cast

        with self.session() as session:
            column = getattr(model, date_column)
            if isinstance(target_date, datetime):
                target_date = target_date.date()
            stmt = select(model).where(cast(column, Date) == target_date)
            if order_by is not None:
                stmt = stmt.order_by(order_by)
            if offset:
                stmt = stmt.offset(offset)
            if limit:
                stmt = stmt.limit(limit)
            return session.execute(stmt).scalars().all()

    def find_by_date_range(
        self,
        model: type[T],
        date_column: str,
        start_date: Any,
        end_date: Any,
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        from datetime import datetime

        with self.session() as session:
            column = getattr(model, date_column)
            if hasattr(end_date, "replace"):
                if not isinstance(end_date, datetime):
                    end_date = datetime.combine(end_date, datetime.max.time())
                elif end_date.time() == datetime.min.time():
                    end_date = end_date.replace(
                        hour=23, minute=59, second=59, microsecond=999999
                    )
            stmt = select(model).where(column >= start_date, column <= end_date)
            if order_by is not None:
                stmt = stmt.order_by(order_by)
            if offset:
                stmt = stmt.offset(offset)
            if limit:
                stmt = stmt.limit(limit)
            return session.execute(stmt).scalars().all()

    def find_by_week(
        self,
        model: type[T],
        date_column: str,
        year: int,
        week: int,
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        from datetime import datetime, timedelta

        jan_4 = datetime(year, 1, 4)
        week_start = jan_4 - timedelta(days=jan_4.weekday()) + timedelta(weeks=week - 1)
        week_end = week_start + timedelta(days=6, hours=23, minutes=59, seconds=59)
        with self.session() as session:
            column = getattr(model, date_column)
            stmt = select(model).where(column >= week_start, column <= week_end)
            if order_by is not None:
                stmt = stmt.order_by(order_by)
            if offset:
                stmt = stmt.offset(offset)
            if limit:
                stmt = stmt.limit(limit)
            return session.execute(stmt).scalars().all()

    def find_by_month(
        self,
        model: type[T],
        date_column: str,
        year: int,
        month: int,
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        from calendar import monthrange
        from datetime import datetime

        start_date = datetime(year, month, 1)
        last_day = monthrange(year, month)[1]
        end_date = datetime(year, month, last_day, 23, 59, 59, 999999)
        with self.session() as session:
            column = getattr(model, date_column)
            stmt = select(model).where(column >= start_date, column <= end_date)
            if order_by is not None:
                stmt = stmt.order_by(order_by)
            if offset:
                stmt = stmt.offset(offset)
            if limit:
                stmt = stmt.limit(limit)
            return session.execute(stmt).scalars().all()

    def find_by_year(
        self,
        model: type[T],
        date_column: str,
        year: int,
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        from datetime import datetime

        start_date = datetime(year, 1, 1)
        end_date = datetime(year, 12, 31, 23, 59, 59, 999999)
        with self.session() as session:
            column = getattr(model, date_column)
            stmt = select(model).where(column >= start_date, column <= end_date)
            if order_by is not None:
                stmt = stmt.order_by(order_by)
            if offset:
                stmt = stmt.offset(offset)
            if limit:
                stmt = stmt.limit(limit)
            return session.execute(stmt).scalars().all()

    def find_by_today(
        self,
        model: type[T],
        date_column: str,
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        from datetime import date

        return self.find_by_date(
            model, date_column, date.today(),
            limit=limit, offset=offset, order_by=order_by
        )

    def find_by_last_n_days(
        self,
        model: type[T],
        date_column: str,
        days: int,
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        from datetime import datetime, timedelta

        end_date = datetime.now()
        start_date = end_date - timedelta(days=days - 1)
        start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0)
        return self.find_by_date_range(
            model, date_column, start_date, end_date,
            limit=limit, offset=offset, order_by=order_by
        )

    def count_by_date(self, model: type[T], date_column: str, target_date: Any) -> int:
        return len(self.find_by_date(model, date_column, target_date))

    def count_by_date_range(
        self, model: type[T], date_column: str, start_date: Any, end_date: Any
    ) -> int:
        return len(self.find_by_date_range(model, date_column, start_date, end_date))

    def count_by_month(
        self, model: type[T], date_column: str, year: int, month: int
    ) -> int:
        return len(self.find_by_month(model, date_column, year, month))

    def update_instance(self, instance: T) -> T:
        with self.session() as session:
            instance = session.merge(instance)
            session.flush()
            session.refresh(instance)
            return instance

    def update_by_id(
        self, model: type[T], id_: Any, values: dict[str, Any]
    ) -> T | None:
        with self.session() as session:
            instance = session.get(model, id_)
            if instance:
                for key, value in values.items():
                    setattr(instance, key, value)
                session.flush()
                session.refresh(instance)
            return instance

    def update_many(
        self,
        model: type[T],
        filters: dict[str, Any],
        values: dict[str, Any],
    ) -> int:
        with self.session() as session:
            stmt = update(model)
            for key, value in filters.items():
                stmt = stmt.where(getattr(model, key) == value)
            stmt = stmt.values(**values)
            result = session.execute(stmt)
            return result.rowcount or 0

    def delete(self, instance: T) -> None:
        with self.session() as session:
            instance = session.merge(instance)
            session.delete(instance)

    def delete_by_id(self, model: type[T], id_: Any) -> bool:
        with self.session() as session:
            instance = session.get(model, id_)
            if instance:
                session.delete(instance)
                return True
            return False

    def delete_many(self, model: type[T], filters: dict[str, Any]) -> int:
        with self.session() as session:
            stmt = delete(model)
            for key, value in filters.items():
                stmt = stmt.where(getattr(model, key) == value)
            result = session.execute(stmt)
            return result.rowcount or 0

    def execute(
        self, stmt: Select[tuple[T]] | Update | Delete | str
    ) -> Result[Any]:
        with self.session() as session:
            if isinstance(stmt, str):
                stmt = text(stmt)
            return session.execute(stmt)

    def table_exists(self, table_name: str) -> bool:
        return table_name in inspect(self.engine).get_table_names()

    def create_all(self) -> None:
        Base.metadata.create_all(self.engine)

    def drop_all(self) -> None:
        Base.metadata.drop_all(self.engine)

    def dispose(self) -> None:
        self.engine.dispose()

    def __enter__(self) -> DatabaseManager[T]:
        return self

    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        self.dispose()


class AsyncDatabaseManager(Generic[T]):
    """Asynchronous database manager for non-blocking operations."""

    def __init__(self, config: DatabaseConfig | None = None):
        self.config = config or _config
        self.engine: AsyncEngine = create_async_engine(
            self.config.async_url,
            poolclass=QueuePool,
            pool_size=self.config.pool_size,
            max_overflow=self.config.max_overflow,
            pool_timeout=self.config.pool_timeout,
            pool_recycle=self.config.pool_recycle,
            pool_pre_ping=self.config.pool_pre_ping,
            echo=self.config.echo,
            echo_pool=self.config.echo_pool,
        )
        self.session_factory = async_sessionmaker(
            bind=self.engine,
            autoflush=False,
            expire_on_commit=False,
        )

    @asynccontextmanager
    async def session(self) -> AsyncIterator[AsyncSession]:
        session = self.session_factory()
        try:
            yield session
            await session.commit()
        except Exception as e:
            await session.rollback()
            logger.error(f"Session error: {e}", exc_info=True)
            raise
        finally:
            await session.close()

    async def create(self, instance: T) -> T:
        async with self.session() as session:
            session.add(instance)
            await session.flush()
            await session.refresh(instance)
            return instance

    async def create_many(self, instances: Sequence[T]) -> Sequence[T]:
        async with self.session() as session:
            session.add_all(instances)
            await session.flush()
            for instance in instances:
                await session.refresh(instance)
            return instances

    async def get_by_id(self, model: type[T], id_: Any) -> T | None:
        async with self.session() as session:
            return await session.get(model, id_)

    async def get_all(
        self,
        model: type[T],
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        async with self.session() as session:
            stmt = select(model)
            if order_by is not None:
                stmt = stmt.order_by(order_by)
            if offset:
                stmt = stmt.offset(offset)
            if limit:
                stmt = stmt.limit(limit)
            result = await session.execute(stmt)
            return result.scalars().all()

    async def find(
        self,
        model: type[T],
        *,
        filters: dict[str, Any] | None = None,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        async with self.session() as session:
            stmt = select(model)
            if filters:
                for key, value in filters.items():
                    stmt = stmt.where(getattr(model, key) == value)
            if order_by is not None:
                stmt = stmt.order_by(order_by)
            if offset:
                stmt = stmt.offset(offset)
            if limit:
                stmt = stmt.limit(limit)
            result = await session.execute(stmt)
            return result.scalars().all()

    async def find_one(self, model: type[T], *, filters: dict[str, Any]) -> T | None:
        async with self.session() as session:
            stmt = select(model)
            for key, value in filters.items():
                stmt = stmt.where(getattr(model, key) == value)
            result = await session.execute(stmt)
            return result.scalar_one_or_none()

    async def count(
        self, model: type[T], *, filters: dict[str, Any] | None = None
    ) -> int:
        async with self.session() as session:
            stmt = select(model)
            if filters:
                for key, value in filters.items():
                    stmt = stmt.where(getattr(model, key) == value)
            result = await session.execute(stmt)
            return len(result.scalars().all())

    async def exists(self, model: type[T], *, filters: dict[str, Any]) -> bool:
        return await self.find_one(model, filters=filters) is not None

    async def find_by_date(
        self,
        model: type[T],
        date_column: str,
        target_date: Any,
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        from datetime import datetime

        from sqlalchemy import Date, cast

        async with self.session() as session:
            column = getattr(model, date_column)
            if isinstance(target_date, datetime):
                target_date = target_date.date()
            stmt = select(model).where(cast(column, Date) == target_date)
            if order_by is not None:
                stmt = stmt.order_by(order_by)
            if offset:
                stmt = stmt.offset(offset)
            if limit:
                stmt = stmt.limit(limit)
            result = await session.execute(stmt)
            return result.scalars().all()

    async def find_by_date_range(
        self,
        model: type[T],
        date_column: str,
        start_date: Any,
        end_date: Any,
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        from datetime import datetime

        async with self.session() as session:
            column = getattr(model, date_column)
            if hasattr(end_date, "replace"):
                if not isinstance(end_date, datetime):
                    end_date = datetime.combine(end_date, datetime.max.time())
                elif end_date.time() == datetime.min.time():
                    end_date = end_date.replace(
                        hour=23, minute=59, second=59, microsecond=999999
                    )
            stmt = select(model).where(column >= start_date, column <= end_date)
            if order_by is not None:
                stmt = stmt.order_by(order_by)
            if offset:
                stmt = stmt.offset(offset)
            if limit:
                stmt = stmt.limit(limit)
            result = await session.execute(stmt)
            return result.scalars().all()

    async def find_by_week(
        self,
        model: type[T],
        date_column: str,
        year: int,
        week: int,
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        from datetime import datetime, timedelta

        jan_4 = datetime(year, 1, 4)
        week_start = jan_4 - timedelta(days=jan_4.weekday()) + timedelta(weeks=week - 1)
        week_end = week_start + timedelta(days=6, hours=23, minutes=59, seconds=59)
        async with self.session() as session:
            column = getattr(model, date_column)
            stmt = select(model).where(column >= week_start, column <= week_end)
            if order_by is not None:
                stmt = stmt.order_by(order_by)
            if offset:
                stmt = stmt.offset(offset)
            if limit:
                stmt = stmt.limit(limit)
            result = await session.execute(stmt)
            return result.scalars().all()

    async def find_by_month(
        self,
        model: type[T],
        date_column: str,
        year: int,
        month: int,
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        from calendar import monthrange
        from datetime import datetime

        start_date = datetime(year, month, 1)
        last_day = monthrange(year, month)[1]
        end_date = datetime(year, month, last_day, 23, 59, 59, 999999)
        async with self.session() as session:
            column = getattr(model, date_column)
            stmt = select(model).where(column >= start_date, column <= end_date)
            if order_by is not None:
                stmt = stmt.order_by(order_by)
            if offset:
                stmt = stmt.offset(offset)
            if limit:
                stmt = stmt.limit(limit)
            result = await session.execute(stmt)
            return result.scalars().all()

    async def find_by_year(
        self,
        model: type[T],
        date_column: str,
        year: int,
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        from datetime import datetime

        start_date = datetime(year, 1, 1)
        end_date = datetime(year, 12, 31, 23, 59, 59, 999999)
        async with self.session() as session:
            column = getattr(model, date_column)
            stmt = select(model).where(column >= start_date, column <= end_date)
            if order_by is not None:
                stmt = stmt.order_by(order_by)
            if offset:
                stmt = stmt.offset(offset)
            if limit:
                stmt = stmt.limit(limit)
            result = await session.execute(stmt)
            return result.scalars().all()

    async def find_by_today(
        self,
        model: type[T],
        date_column: str,
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        from datetime import date

        return await self.find_by_date(
            model, date_column, date.today(),
            limit=limit, offset=offset, order_by=order_by
        )

    async def find_by_last_n_days(
        self,
        model: type[T],
        date_column: str,
        days: int,
        *,
        limit: int | None = None,
        offset: int | None = None,
        order_by: Any = None,
    ) -> Sequence[T]:
        from datetime import datetime, timedelta

        end_date = datetime.now()
        start_date = end_date - timedelta(days=days - 1)
        start_date = start_date.replace(hour=0, minute=0, second=0, microsecond=0)
        return await self.find_by_date_range(
            model, date_column, start_date, end_date,
            limit=limit, offset=offset, order_by=order_by
        )

    async def count_by_date(
        self, model: type[T], date_column: str, target_date: Any
    ) -> int:
        return len(await self.find_by_date(model, date_column, target_date))

    async def count_by_date_range(
        self,
        model: type[T],
        date_column: str,
        start_date: Any,
        end_date: Any,
    ) -> int:
        return len(
            await self.find_by_date_range(model, date_column, start_date, end_date)
        )

    async def count_by_month(
        self,
        model: type[T],
        date_column: str,
        year: int,
        month: int,
    ) -> int:
        return len(await self.find_by_month(model, date_column, year, month))

    async def update_instance(self, instance: T) -> T:
        async with self.session() as session:
            instance = await session.merge(instance)
            await session.flush()
            await session.refresh(instance)
            return instance

    async def update_by_id(
        self, model: type[T], id_: Any, values: dict[str, Any]
    ) -> T | None:
        async with self.session() as session:
            instance = await session.get(model, id_)
            if instance:
                for key, value in values.items():
                    setattr(instance, key, value)
                await session.flush()
                await session.refresh(instance)
            return instance

    async def update_many(
        self,
        model: type[T],
        filters: dict[str, Any],
        values: dict[str, Any],
    ) -> int:
        async with self.session() as session:
            stmt = update(model)
            for key, value in filters.items():
                stmt = stmt.where(getattr(model, key) == value)
            stmt = stmt.values(**values)
            result = await session.execute(stmt)
            return result.rowcount or 0

    async def delete(self, instance: T) -> None:
        async with self.session() as session:
            instance = await session.merge(instance)
            await session.delete(instance)

    async def delete_by_id(self, model: type[T], id_: Any) -> bool:
        async with self.session() as session:
            instance = await session.get(model, id_)
            if instance:
                await session.delete(instance)
                return True
            return False

    async def delete_many(
        self, model: type[T], filters: dict[str, Any]
    ) -> int:
        async with self.session() as session:
            stmt = delete(model)
            for key, value in filters.items():
                stmt = stmt.where(getattr(model, key) == value)
            result = await session.execute(stmt)
            return result.rowcount or 0

    async def execute(
        self, stmt: Select[tuple[T]] | Update | Delete | str
    ) -> Result[Any]:
        async with self.session() as session:
            if isinstance(stmt, str):
                stmt = text(stmt)
            return await session.execute(stmt)

    async def create_all(self) -> None:
        async with self.engine.begin() as conn:
            await conn.run_sync(Base.metadata.create_all)

    async def drop_all(self) -> None:
        async with self.engine.begin() as conn:
            await conn.run_sync(Base.metadata.drop_all)

    async def dispose(self) -> None:
        await self.engine.dispose()

    async def __aenter__(self) -> AsyncDatabaseManager[T]:
        return self

    async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        await self.dispose()


__all__ = [
    "Base",
    "DatabaseConfig",
    "DatabaseManager",
    "AsyncDatabaseManager",
    "engine",
    "SessionLocal",
    "get_db_session",
]
