"""This module provides factories to access the admin and project databases.

An instance of this class can be used to connect the admin or a project database.
"""
import collections
from typing import Iterator

from sqlalchemy import create_engine
from sqlalchemy.engine import Engine
from sqlalchemy.future import select
from sqlalchemy.orm import sessionmaker

from ..config import settings
from .models import DBProject

assert settings.SQLALCHEMY_DBADMIN_URI is not None

engine = create_engine(settings.SQLALCHEMY_DBADMIN_URI, pool_pre_ping=True, future=True)
AdminSession = sessionmaker(autocommit=False, autoflush=False, bind=engine, future=True)


class ProjectEngines(collections.abc.Mapping):
    """Cache for the project engines."""

    def __init__(self) -> None:
        """Constructs a new cache for the project engines."""
        self._dict: dict[str, Engine] = {}

    def __getitem__(self, project_name: str) -> Engine:
        """Returns the engine associated to the project."""
        engine = self._dict.get(project_name)
        if engine is None:
            engine = self._create_project_engine(project_name)
            self._dict[project_name] = engine
        return engine

    def __len__(self) -> int:
        """Returns the number of cached engines."""
        return len(self._dict)

    def __iter__(self) -> Iterator[str]:
        """Iterates through the cached engines."""
        return iter(self._dict)

    @staticmethod
    def _create_project_engine(project_name: str) -> Engine:
        """Factory for the project engines."""
        stmt = select(DBProject).where(DBProject.name == project_name)
        project = AdminSession().execute(stmt).scalar_one()
        uri = project.uri
        return create_engine(uri, pool_pre_ping=True, future=True)


project_engines = ProjectEngines()