"""Warning calculation for the settings UI.

Thin orchestrator: reads settings from a single DB session,
delegates to pure check functions in hardware.py and context.py.
"""

from typing import List

from flask import session
from loguru import logger

from ...database.session_context import get_user_db_session
from ...utilities.db_utils import get_settings_manager
from .context import (
    check_context_below_history,
    check_context_truncation_history,
)
from .backup import (
    check_backup_disabled,
    check_backup_healthy,
    check_no_backups_exist,
)
from .hardware import (
    LOCAL_PROVIDERS,
    check_high_context,
    check_legacy_server_config,
    check_model_mismatch,
)
from ...security.egress.policy import DEFAULT_EGRESS_SCOPE
from ...security.egress.warnings import (
    check_cloud_embeddings_enabled,
    check_cloud_llm_enabled,
    check_effective_scope,
    check_public_egress_enabled,
)
from ...constants import DEFAULT_SEARCH_TOOL


def _safe_check(check_fn, *args, **kwargs):
    """Run a single warning check, returning None on failure."""
    try:
        return check_fn(*args, **kwargs)
    except Exception:
        name = getattr(check_fn, "__name__", repr(check_fn))
        logger.exception(f"Warning check {name} failed")
        return None


def calculate_warnings() -> List[dict]:
    """Calculate current warning conditions based on settings.

    Uses a single DB session for all setting reads and history queries.
    """
    warnings: List[dict] = []

    try:
        username = session.get("username")
        with get_user_db_session(username) as db_session:
            if not db_session:
                return []

            settings_manager = get_settings_manager(db_session, username)

            # Read all needed settings in one session
            provider = settings_manager.get_setting(
                "llm.provider", "ollama"
            ).lower()
            local_context = settings_manager.get_setting(
                "llm.local_context_window_size", 8192
            )
            current_model = settings_manager.get_setting("llm.model", "")
            dismiss_high_context = settings_manager.get_setting(
                "app.warnings.dismiss_high_context", False
            )
            dismiss_model_mismatch = settings_manager.get_setting(
                "app.warnings.dismiss_model_mismatch", False
            )
            dismiss_context_warning = settings_manager.get_setting(
                "app.warnings.dismiss_context_reduced", False
            )
            dismiss_legacy_config = settings_manager.get_setting(
                "app.warnings.dismiss_legacy_config", False
            )
            backup_enabled = settings_manager.get_setting(
                "backup.enabled", True
            )
            dismiss_backup_disabled = settings_manager.get_setting(
                "app.warnings.dismiss_backup_disabled", False
            )
            dismiss_no_backups = settings_manager.get_setting(
                "app.warnings.dismiss_no_backups", False
            )

            logger.debug(f"Starting warning calculation - provider={provider}")

            is_local = provider in LOCAL_PROVIDERS

            # --- Hardware / settings checks (pure functions) ---
            w = _safe_check(
                check_high_context,
                provider,
                local_context,
                dismiss_high_context,
            )
            if w:
                warnings.append(w)

            w = _safe_check(
                check_model_mismatch,
                provider,
                current_model,
                local_context,
                dismiss_model_mismatch,
            )
            if w:
                warnings.append(w)

            w = _safe_check(check_legacy_server_config, dismiss_legacy_config)
            if w:
                warnings.append(w)

            # --- Egress policy checks ---
            egress_scope = settings_manager.get_setting(
                "policy.egress_scope", DEFAULT_EGRESS_SCOPE
            )
            require_local_endpoint = bool(
                settings_manager.get_setting(
                    "llm.require_local_endpoint", False
                )
            )
            embeddings_provider = settings_manager.get_setting(
                "embeddings.provider", ""
            )
            embeddings_base_url = settings_manager.get_setting(
                "embeddings.openai.base_url", ""
            )
            require_local_embeddings = bool(
                settings_manager.get_setting("embeddings.require_local", False)
            )
            primary_engine = settings_manager.get_setting(
                "search.tool", DEFAULT_SEARCH_TOOL
            )

            # Resolve the EFFECTIVE posture so the banners are accurate. For
            # `adaptive`, this turns the opaque "follows the primary" into a
            # concrete scope; it also applies the PRIVATE_ONLY -> force-local
            # coupling, so a private-resolving run doesn't falsely show the
            # "cloud LLM enabled" banner. Best-effort: any failure falls back
            # to the raw values (the page must never break on this).
            effective_scope = str(egress_scope).lower()
            effective_require_local_endpoint = require_local_endpoint
            effective_require_local_embeddings = require_local_embeddings
            try:
                from ...security.egress.policy import context_from_snapshot

                _snap = settings_manager.get_settings_snapshot()
                if isinstance(_snap, dict):
                    # allow_dns=False: this runs on the /api/warnings page-
                    # render hot path; skip the synchronous getaddrinfo that
                    # ADAPTIVE resolution would otherwise do for a URL-engine
                    # primary (could block the render up to _DNS_TIMEOUT_SEC).
                    # The banner is advisory and falls back to static
                    # classification — accuracy here is best-effort by design.
                    _eff_ctx = context_from_snapshot(
                        _snap,
                        primary_engine or DEFAULT_SEARCH_TOOL,
                        username=username,
                        allow_dns=False,
                    )
                    effective_scope = _eff_ctx.scope.value
                    effective_require_local_endpoint = (
                        _eff_ctx.require_local_llm
                    )
                    effective_require_local_embeddings = (
                        _eff_ctx.require_local_embeddings
                    )
            except Exception:
                logger.debug(
                    "could not resolve effective egress scope for warnings",
                    exc_info=True,
                )

            adaptive_info_dismissed = bool(
                settings_manager.get_setting(
                    "app.warnings.dismiss_adaptive_scope_info", False
                )
            )

            # Each egress banner has its OWN dismiss flag. Previously all
            # three shared app.warnings.dismiss_egress_policy, so dismissing
            # the fresh-install "public egress" notice ALSO permanently hid
            # the critical cloud-LLM / cloud-embeddings warnings — a
            # false-safety trap (switch to OpenAI later, never warned).
            public_egress_dismissed = bool(
                settings_manager.get_setting(
                    "app.warnings.dismiss_egress_policy", False
                )
            )
            cloud_llm_dismissed = bool(
                settings_manager.get_setting(
                    "app.warnings.dismiss_cloud_llm", False
                )
            )
            cloud_embeddings_dismissed = bool(
                settings_manager.get_setting(
                    "app.warnings.dismiss_cloud_embeddings", False
                )
            )

            # Informational: state what ADAPTIVE actually resolves to.
            w = _safe_check(
                check_effective_scope,
                egress_scope,
                effective_scope,
                primary_engine,
                adaptive_info_dismissed,
            )
            if w:
                warnings.append(w)

            w = _safe_check(
                check_public_egress_enabled,
                effective_scope,
                public_egress_dismissed,
            )
            if w:
                warnings.append(w)

            w = _safe_check(
                check_cloud_llm_enabled,
                provider,
                effective_require_local_endpoint,
                cloud_llm_dismissed,
            )
            if w:
                warnings.append(w)

            w = _safe_check(
                check_cloud_embeddings_enabled,
                embeddings_provider,
                embeddings_base_url,
                effective_require_local_embeddings,
                cloud_embeddings_dismissed,
            )
            if w:
                warnings.append(w)

            # --- Backup checks ---
            w = _safe_check(
                check_backup_disabled, backup_enabled, dismiss_backup_disabled
            )
            if w:
                warnings.append(w)

            # Check backup file status (lightweight filesystem glob)
            dismiss_backup_info = settings_manager.get_setting(
                "app.warnings.dismiss_backup_info", False
            )
            try:
                from ...config.paths import get_user_backup_directory
                from ...utilities.formatting import human_size
                from ...database.backup.backup_service import (
                    is_safe_glob_result,
                )

                username = session.get("username")
                if username:
                    backup_dir = get_user_backup_directory(username)
                    total_size = 0
                    backup_count = 0
                    for f in backup_dir.glob("ldr_backup_*.db"):
                        # Skip symlinks / entries resolving outside backup_dir
                        # so a planted symlink can't inflate the count/size
                        # shown in warnings — same hardening as BackupService.
                        if not is_safe_glob_result(f, backup_dir):
                            continue
                        try:
                            total_size += f.stat().st_size
                            backup_count += 1
                        except FileNotFoundError:
                            continue

                    w = _safe_check(
                        check_no_backups_exist,
                        backup_enabled,
                        backup_count,
                        dismiss_no_backups,
                    )
                    if w:
                        warnings.append(w)

                    w = _safe_check(
                        check_backup_healthy,
                        backup_enabled,
                        backup_count,
                        human_size(total_size),
                        dismiss_backup_info,
                    )
                    if w:
                        warnings.append(w)
            except Exception:
                logger.debug("Backup status check skipped")

            # --- History-based checks (need DB queries) ---
            if is_local and not dismiss_context_warning:
                w = _safe_check(
                    check_context_below_history, db_session, local_context
                )
                if w:
                    warnings.append(w)

                w = _safe_check(
                    check_context_truncation_history, db_session, local_context
                )
                if w:
                    warnings.append(w)

    except Exception:
        logger.exception("Error calculating warnings")

    return warnings
