from functools import cache
from typing import Any

from sqlalchemy import URL
from sqlalchemy import StaticPool
from sqlalchemy.ext.asyncio import AsyncEngine
from sqlalchemy.ext.asyncio import create_async_engine

from ..config import PostgresSettings
from ..config import Settings
from ..config import SqliteSettings
from ..config import get_settings


def get_database_url(settings: Settings) -> str:
    match settings.database:
        case SqliteSettings(path=path):
            url = URL.create(
                drivername="sqlite+aiosqlite",
                database=path,
            )
        case PostgresSettings() as database:
            url = URL.create(
                drivername="postgresql+asyncpg",
                host=database.host,
                port=database.port,
                database=database.database,
                username=database.user,
                password=database.password.get_secret_value(),
            )
    return url.render_as_string(hide_password=False)


def resolve_engine(settings: Settings | None = None) -> AsyncEngine:
    active_settings = settings or get_settings()
    return get_engine(get_database_url(active_settings))


@cache
def get_engine(database_url: str) -> AsyncEngine:
    kwargs: dict[str, Any] = {"echo": False, "pool_pre_ping": True}
    if database_url.startswith("sqlite"):
        kwargs["connect_args"] = {"check_same_thread": False}
        if ":memory:" in database_url:
            kwargs["poolclass"] = StaticPool

    return create_async_engine(database_url, **kwargs)


async def dispose_engine(database_url: str) -> None:
    engine = get_engine(database_url)
    try:
        await engine.dispose()
    finally:
        get_engine.cache_clear()


async def refresh_engine(database_url: str) -> AsyncEngine:
    await dispose_engine(database_url)
    return get_engine(database_url)
