from typing import Any, Dict, Optional, Union

from loguru import logger
from sqlalchemy.orm import Session

from ...database.models import Setting
from ...utilities.db_utils import get_settings_manager


def set_setting(
    key: str,
    value: Any,
    commit: bool = True,
    db_session: Optional[Session] = None,
) -> bool:
    """
    Set a setting value

    Args:
        key: Setting key
        value: Setting value
        commit: Whether to commit the change
        db_session: Optional database session

    Returns:
        bool: True if successful
    """
    manager = get_settings_manager(db_session)
    return bool(manager.set_setting(key, value, commit))


def get_all_settings() -> Dict[str, Any]:
    """
    Get all settings, optionally filtered by type

    Returns:
        Dict[str, Any]: Dictionary of settings

    """
    manager = get_settings_manager()
    return manager.get_all_settings()  # type: ignore[no-any-return]


def create_or_update_setting(
    setting: Union[Dict[str, Any], Setting],
    commit: bool = True,
    db_session: Optional[Session] = None,
) -> Optional[Setting]:
    """
    Create or update a setting

    Args:
        setting: Setting dictionary or object
        commit: Whether to commit the change
        db_session: Optional database session

    Returns:
        Optional[Setting]: The setting object if successful
    """
    manager = get_settings_manager(db_session)
    return manager.create_or_update_setting(setting, commit)  # type: ignore[no-any-return]


def invalidate_settings_caches(username=None):
    """Invalidate all settings-related caches after a settings mutation.

    Call this after any route that creates, updates, or deletes settings
    so background services pick up changes immediately.

    Safe to call from anywhere — silently skips caches that aren't initialized.

    Args:
        username: If provided, invalidates only that user's scheduler cache.
                  If None, invalidates all users' scheduler caches.
    """
    # News scheduler per-user settings cache (TTLCache, 5-min TTL).
    # NOTE: the LLM provider dropdown is served by the auto-discovery path
    # (llm/providers/auto_discovery.get_discovered_provider_options), which
    # rebuilds per call and has no cache to invalidate here.
    try:
        from ...scheduler.background import get_background_job_scheduler

        scheduler = get_background_job_scheduler()
        if username is not None:
            scheduler.invalidate_user_settings_cache(username)
        else:
            scheduler.invalidate_all_settings_cache()
    except Exception:
        logger.debug("Could not invalidate scheduler cache", exc_info=True)


def reschedule_document_jobs_if_needed(username, changed_keys):
    """Reschedule a user's document-scheduler jobs after a settings change.

    Cache invalidation alone (``invalidate_settings_caches``) only clears the
    scheduler's cached settings — it does not (re)create or tear down the
    per-user interval jobs. So toggling ``document_scheduler.*`` settings (e.g.
    ``sweep_library_collections`` or the legacy ``generate_rag``) would not take
    effect until the user logged out and back in. Call this AFTER
    ``invalidate_settings_caches`` so the scheduler re-reads fresh settings and
    the change applies on the next tick.

    Only reschedules when a ``document_scheduler.*`` key actually changed, so an
    unrelated settings save never churns (and resets the timer of) the document
    jobs. Best-effort and silent when the scheduler isn't running (e.g. tests,
    CLI), mirroring ``invalidate_settings_caches``.

    Args:
        username: The user whose jobs should be re-evaluated.
        changed_keys: Iterable of setting keys written by the request.
    """
    if not username or not changed_keys:
        return
    if not any(
        str(key).startswith("document_scheduler.") for key in changed_keys
    ):
        return
    try:
        from ...scheduler.background import get_background_job_scheduler

        scheduler = get_background_job_scheduler()
        scheduler.reschedule_document_jobs(username)
    except Exception:
        logger.debug(
            "Could not reschedule document jobs after settings change",
            exc_info=True,
        )


def validate_setting(
    setting: Setting, value: Any
) -> tuple[bool, Optional[str]]:
    """
    Validate a setting value based on its type and constraints

    Args:
        setting: The Setting object to validate against
        value: The value to validate

    Returns:
        tuple[bool, Optional[str]]: (is_valid, error_message)
    """
    from ..routes.settings_routes import (
        validate_setting as routes_validate_setting,
    )

    return routes_validate_setting(setting, value)
