import os
from typing import Any, Dict

from cachetools import LRUCache
from loguru import logger
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker

from ..utilities.threading_utils import thread_specific_cache
from ..web.services.settings_manager import SettingsManager

# Database path.
DATA_DIR = os.path.abspath(
    os.path.join(os.path.dirname(__file__), "..", "..", "data")
)
DB_PATH = os.path.join(DATA_DIR, "ldr.db")


@thread_specific_cache(cache=LRUCache(maxsize=10))
def get_db_session(_namespace: str = "") -> Session:
    """
    Args:
        _namespace: This can be specified to an arbitrary string in order to
            force the caching mechanism to create separate settings even in
            the same thread. Usually it does not need to be specified.

    Returns:
        The singleton DB session for each thread.

    """
    engine = create_engine(f"sqlite:///{DB_PATH}")
    session_class = sessionmaker(bind=engine)
    return session_class()


def get_settings_manager() -> SettingsManager:
    """
    Returns:
        The singleton settings manager.

    """
    return SettingsManager(db_session=get_db_session())


def get_db_setting(
    key: str, default_value: Any | None = None
) -> str | Dict[str, Any] | None:
    """
    Get a setting from the database with fallback to default value

    Args:
        key: The setting key.
        default_value: If the setting is not found, it will return this instead.

    Returns:
        The setting value.

    """
    try:
        # Get settings manager which handles database access
        return get_settings_manager().get_setting(key, default=default_value)
    except Exception:
        logger.exception(f"Error getting setting {key} from database")

    logger.warning(f"Could not read setting '{key}' from the database.")
    return default_value
