"""
Utilities for managing settings in the programmatic API.

This module provides functions to create settings snapshots for the API
without requiring database access, reusing the same mechanisms as the
web interface.
"""

import copy
from typing import Any
from loguru import logger

from ..settings import SettingsManager
from ..settings.base import ISettingsManager
from ..settings.manager import UI_ELEMENT_TO_SETTING_TYPE, check_env_setting
from ..utilities.type_utils import to_bool, unwrap_setting


class InMemorySettingsManager(ISettingsManager):
    """
    In-memory settings manager that doesn't require database access.

    This is used for the programmatic API to provide settings without
    needing a database connection.
    """

    def __init__(self):
        """Initialize with default settings from JSON file."""
        # Create a base manager to get default settings
        self._base_manager = SettingsManager(db_session=None)
        self._settings = {}
        self._load_defaults()

    def _get_typed_value(self, setting_data: dict[str, Any], value: Any) -> Any:
        """
        Convert a value to the appropriate type based on the setting's ui_element.

        Args:
            setting_data: The setting metadata containing ui_element
            value: The value to convert

        Returns:
            The typed value, or the original value if conversion fails
        """
        if value is None:
            return None

        ui_element = setting_data.get("ui_element", "text")
        setting_type = UI_ELEMENT_TO_SETTING_TYPE.get(ui_element)

        if setting_type is None:
            logger.warning(
                f"Unknown ui_element type: {ui_element}, returning value as-is"
            )
            return value

        try:
            return setting_type(value)
        except (ValueError, TypeError):
            logger.warning(
                f"Failed to convert value {value} to type {setting_type}"
            )
            return value

    def _load_defaults(self):
        """Load default settings from the JSON file."""
        # Get default settings from the base manager
        defaults = self._base_manager.default_settings

        # Convert to the format expected by get_all_settings
        for key, setting_data in defaults.items():
            self._settings[key] = setting_data.copy()

            # Check environment variable override
            env_value = check_env_setting(key)
            if env_value is not None:
                # Use the typed value conversion
                self._settings[key]["value"] = self._get_typed_value(
                    setting_data, env_value
                )

        # Load search engine configurations from individual JSON files
        from importlib import resources
        import json

        try:
            # Load search engines from defaults/settings/search_engines/
            search_engines_dir = resources.files(
                "local_deep_research.defaults.settings"
            ).joinpath("search_engines")

            if search_engines_dir.exists() and search_engines_dir.is_dir():
                for json_file in search_engines_dir.glob("*.json"):
                    try:
                        engine_settings = json.loads(
                            json_file.read_text(encoding="utf-8-sig")
                        )
                        # Merge into main settings
                        for key, setting_data in engine_settings.items():
                            if key not in self._settings:
                                self._settings[key] = setting_data.copy()
                    except Exception:
                        logger.warning(
                            f"Failed to load search engine config from {json_file.name}"
                        )
        except Exception:
            logger.warning("Failed to load search engine configs")

    def get_setting(
        self, key: str, default: Any = None, check_env: bool = True
    ) -> Any:
        """Get a setting value."""
        if key in self._settings:
            setting_data = self._settings[key]
            value = setting_data.get("value", default)
            # Ensure the value has the correct type
            return self._get_typed_value(setting_data, value)
        return default

    def set_setting(self, key: str, value: Any, commit: bool = True) -> bool:
        """Set a setting value (in memory only)."""
        if key in self._settings:
            # Validate and convert the value to the correct type
            typed_value = self._get_typed_value(self._settings[key], value)
            self._settings[key]["value"] = typed_value
            return True
        return False

    def get_all_settings(self) -> dict[str, Any]:
        """Get all settings with metadata."""
        return copy.deepcopy(self._settings)

    def load_from_defaults_file(
        self, commit: bool = True, **kwargs: Any
    ) -> None:
        """Reload defaults (already done in __init__)."""
        self._load_defaults()

    def create_or_update_setting(
        self, setting: dict[str, Any] | Any, commit: bool = True
    ) -> Any | None:
        """Create or update a setting (in memory only)."""
        if isinstance(setting, dict) and "key" in setting:
            key = setting["key"]
            # If the setting has a value, ensure it has the correct type
            if "value" in setting:
                typed_value = self._get_typed_value(setting, setting["value"])
                setting = setting.copy()  # Don't modify the original
                setting["value"] = typed_value
            self._settings[key] = setting
            return setting
        return None

    def delete_setting(self, key: str, commit: bool = True) -> bool:
        """Delete a setting (in memory only)."""
        if key in self._settings:
            del self._settings[key]
            return True
        return False

    def get_bool_setting(
        self, key: str, default: bool = False, check_env: bool = True
    ) -> bool:
        """Get a setting value as a boolean."""
        value = self.get_setting(key, default, check_env)
        return to_bool(value, default)

    def get_settings_snapshot(self) -> dict[str, Any]:
        """Get a simplified settings snapshot with just key-value pairs."""
        all_settings = self.get_all_settings()
        snapshot = {}
        for key, setting in all_settings.items():
            if isinstance(setting, dict) and "value" in setting:
                snapshot[key] = setting["value"]
            else:
                snapshot[key] = setting
        return snapshot

    def import_settings(
        self,
        settings_data: dict[str, Any],
        commit: bool = True,
        overwrite: bool = True,
        delete_extra: bool = False,
    ) -> None:
        """Import settings from a dictionary."""
        if delete_extra:
            self._settings.clear()

        for key, value in settings_data.items():
            if overwrite or key not in self._settings:
                # Ensure proper type handling for imported settings
                if isinstance(value, dict) and "value" in value:
                    typed_value = self._get_typed_value(value, value["value"])
                    value = value.copy()
                    value["value"] = typed_value
                self._settings[key] = value


def get_default_settings_snapshot() -> dict[str, Any]:
    """
    Get a complete settings snapshot with default values.

    This uses the same mechanism as the web interface but without
    requiring database access. Environment variables are checked
    for overrides.

    Returns:
        Dict mapping setting keys to their values and metadata
    """
    manager = InMemorySettingsManager()
    return manager.get_all_settings()


def create_settings_snapshot(
    overrides: dict[str, Any] | None = None,
    base_settings: dict[str, Any] | None = None,
    **kwargs,
) -> dict[str, Any]:
    """
    Create a settings snapshot for the programmatic API.

    Args:
        overrides: Dict of setting overrides (e.g., {"llm.provider": "openai"})
                   This is the most common use case - pass a dict of settings to override.
        base_settings: Base settings dict (defaults to get_default_settings_snapshot())
                       Rarely needed - only for advanced use cases.
        **kwargs: Common setting shortcuts:
            - provider: Maps to "llm.provider"
            - api_key: Maps to "llm.{provider}.api_key"
            - temperature: Maps to "llm.temperature"
            - max_search_results: Maps to "search.max_results"
            - search_engines: Maps to enabled search engines

    Returns:
        Complete settings snapshot for use with the API

    Examples:
        # Most common - pass overrides as first argument
        settings = create_settings_snapshot({"search.tool": "wikipedia"})

        # Or use named parameter
        settings = create_settings_snapshot(overrides={"llm.provider": "openai"})

        # Use kwargs shortcuts
        settings = create_settings_snapshot(provider="openai", temperature=0.7)

        # Advanced - provide custom base settings
        settings = create_settings_snapshot(
            overrides={"search.tool": "wikipedia"},
            base_settings=my_custom_defaults
        )
    """
    # Start with base settings or defaults
    if base_settings is None:
        settings = get_default_settings_snapshot()
    else:
        settings = copy.deepcopy(base_settings)

    # Apply overrides if provided
    if overrides:
        for key, value in overrides.items():
            if key in settings:
                if isinstance(settings[key], dict) and "value" in settings[key]:
                    settings[key]["value"] = value
                else:
                    settings[key] = value
            else:
                # Create a simple setting entry for unknown keys
                # Infer ui_element from value type
                ui_element = "text"  # default
                if isinstance(value, bool):
                    ui_element = "checkbox"
                elif isinstance(value, (int, float)):
                    ui_element = "number"
                elif isinstance(value, dict):
                    ui_element = "json"

                settings[key] = {"value": value, "ui_element": ui_element}

    # Handle common kwargs shortcuts
    if "provider" in kwargs:
        provider = kwargs["provider"]
        if "llm.provider" in settings:
            settings["llm.provider"]["value"] = provider
        else:
            settings["llm.provider"] = {"value": provider}

        # Handle api_key if provided
        if "api_key" in kwargs:
            api_key = kwargs["api_key"]
            api_key_setting = f"llm.{provider}.api_key"
            if api_key_setting in settings:
                settings[api_key_setting]["value"] = api_key
            else:
                settings[api_key_setting] = {"value": api_key}

    if "temperature" in kwargs:
        if "llm.temperature" in settings:
            settings["llm.temperature"]["value"] = kwargs["temperature"]
        else:
            settings["llm.temperature"] = {"value": kwargs["temperature"]}

    if "max_search_results" in kwargs:
        if "search.max_results" in settings:
            settings["search.max_results"]["value"] = kwargs[
                "max_search_results"
            ]
        else:
            settings["search.max_results"] = {
                "value": kwargs["max_search_results"]
            }

    # Add any other common shortcuts here...

    return settings


def extract_setting_value(
    settings_snapshot: dict[str, Any], key: str, default: Any = None
) -> Any:
    """
    Extract a setting value from a settings snapshot.

    Args:
        settings_snapshot: Settings snapshot dict
        key: Setting key (e.g., "llm.provider")
        default: Default value if not found

    Returns:
        The setting value
    """
    if settings_snapshot is None:
        return default
    if key in settings_snapshot:
        setting = settings_snapshot[key]
        return unwrap_setting(setting)
    return default


def extract_bool_setting(
    settings_snapshot: dict[str, Any], key: str, default: bool = False
) -> bool:
    """
    Extract a boolean setting value from a settings snapshot.

    This is a convenience wrapper around extract_setting_value that
    handles string-to-boolean conversion.

    Args:
        settings_snapshot: Settings snapshot dict
        key: Setting key (e.g., "local_search_normalize_vectors")
        default: Default boolean value if not found

    Returns:
        Boolean value of the setting
    """
    value = extract_setting_value(settings_snapshot, key, default)
    return to_bool(value, default)
