"""Ollama LLM provider for Local Deep Research."""

import requests
from langchain_ollama import ChatOllama
from loguru import logger

from ....config.thread_settings import (
    get_setting_from_snapshot as _get_setting_from_snapshot,
)
from ....utilities.url_utils import normalize_url
from ...llm_registry import register_llm


def get_setting_from_snapshot(
    key, default=None, username=None, settings_snapshot=None
):
    """Get setting from context only - no database access from threads.

    This is a wrapper around the shared function that enables fallback LLM check.
    """
    return _get_setting_from_snapshot(
        key, default, username, settings_snapshot, check_fallback_llm=True
    )


class OllamaProvider:
    """Ollama provider for Local Deep Research.

    This is the Ollama local model provider.
    """

    provider_name = "Ollama"
    default_model = "gemma:latest"
    api_key_setting = "llm.ollama.api_key"  # Optional API key for authenticated Ollama instances

    # Metadata for auto-discovery
    provider_key = "OLLAMA"
    company_name = "Ollama"
    region = "Local"
    country = "Local"
    data_location = "Local"
    is_cloud = False

    @classmethod
    def _get_auth_headers(cls, api_key=None, settings_snapshot=None):
        """Get authentication headers for Ollama API requests.

        Args:
            api_key: Optional API key to use (takes precedence)
            settings_snapshot: Optional settings snapshot to get API key from

        Returns:
            Dict of headers, empty if no API key configured
        """
        headers = {}

        # Use provided API key or get from settings
        if api_key is None and settings_snapshot is not None:
            api_key = get_setting_from_snapshot(
                cls.api_key_setting,
                "",  # Empty string instead of None to avoid NoSettingsContextError
                settings_snapshot=settings_snapshot,
            )

        if api_key:
            # Support Bearer token authentication for proxied Ollama instances
            headers["Authorization"] = f"Bearer {api_key}"

        return headers

    @classmethod
    def list_models_for_api(cls, api_key=None):
        """Get available models from Ollama.

        Args:
            api_key: Optional API key for authentication

        Returns:
            List of model dictionaries with 'value' and 'label' keys
        """
        from ....utilities.llm_utils import fetch_ollama_models
        from ....utilities.db_utils import get_settings_manager

        # Get Ollama URL from settings (need to import here to avoid circular imports)
        settings = get_settings_manager()
        base_url = settings.get_setting("llm.ollama.url")

        if not base_url:
            logger.warning("Ollama URL not configured")
            return []

        base_url = normalize_url(base_url)

        # Get authentication headers
        headers = cls._get_auth_headers(api_key=api_key)

        # Fetch models using centralized function
        models = fetch_ollama_models(
            base_url, timeout=2.0, auth_headers=headers
        )

        # Add provider info and format for LLM API
        for model in models:
            # Clean up the model name for display
            model_name = model["value"]
            display_name = model_name.replace(":latest", "").replace(":", " ")
            model["label"] = f"{display_name} (Ollama)"
            model["provider"] = "OLLAMA"

        logger.info(f"Found {len(models)} Ollama models")
        return models

    @classmethod
    def create_llm(cls, model_name=None, temperature=0.7, **kwargs):
        """Factory function for Ollama LLMs.

        Args:
            model_name: Name of the model to use
            temperature: Model temperature (0.0-1.0)
            **kwargs: Additional arguments including settings_snapshot

        Returns:
            A configured ChatOllama instance

        Raises:
            ValueError: If Ollama is not available
        """
        settings_snapshot = kwargs.get("settings_snapshot")

        # Use default model if none specified
        if not model_name:
            model_name = cls.default_model

        # Use the configurable Ollama base URL
        raw_base_url = get_setting_from_snapshot(
            "llm.ollama.url",
            None,
            settings_snapshot=settings_snapshot,
        )
        if not raw_base_url:
            raise ValueError(
                "Ollama URL not configured. Please set llm.ollama.url in settings."
            )
        base_url = normalize_url(raw_base_url)

        # Check if Ollama is available before trying to use it
        if not cls.is_available(settings_snapshot):
            logger.error(f"Ollama not available at {base_url}.")
            raise ValueError(f"Ollama not available at {base_url}")

        # Check if the requested model exists
        try:
            logger.info(f"Checking if model '{model_name}' exists in Ollama")

            # Get authentication headers
            headers = cls._get_auth_headers(settings_snapshot=settings_snapshot)

            response = requests.get(
                f"{base_url}/api/tags", timeout=3.0, headers=headers
            )
            if response.status_code == 200:
                # Handle both newer and older Ollama API formats
                data = response.json()
                models = []
                if "models" in data:
                    # Newer Ollama API
                    models = data.get("models", [])
                else:
                    # Older Ollama API format
                    models = data

                # Get list of model names
                model_names = [m.get("name", "").lower() for m in models]
                logger.info(
                    f"Available Ollama models: {', '.join(model_names[:5])}{' and more' if len(model_names) > 5 else ''}"
                )

                if model_name.lower() not in model_names:
                    logger.error(
                        f"Model '{model_name}' not found in Ollama. Available models: {', '.join(model_names[:5])}"
                    )
                    raise ValueError(
                        f"Model '{model_name}' not found in Ollama"
                    )
        except Exception:
            logger.exception(
                f"Error checking for model '{model_name}' in Ollama"
            )
            # Continue anyway, let ChatOllama handle potential errors

        logger.info(
            f"Creating ChatOllama with model={model_name}, base_url={base_url}"
        )

        # Build Ollama parameters
        ollama_params = {
            "model": model_name,
            "base_url": base_url,
            "temperature": temperature,
        }

        # Add authentication headers if configured
        headers = cls._get_auth_headers(settings_snapshot=settings_snapshot)
        if headers:
            # ChatOllama supports auth via headers parameter
            ollama_params["headers"] = headers

        # Get context window size from settings for local providers
        context_window_size = get_setting_from_snapshot(
            "llm.local_context_window_size",
            4096,
            settings_snapshot=settings_snapshot,
        )
        if context_window_size is not None:
            ollama_params["num_ctx"] = int(context_window_size)

        # Add max_tokens if specified in settings and supported
        if get_setting_from_snapshot(
            "llm.supports_max_tokens", True, settings_snapshot=settings_snapshot
        ):
            # Use 80% of context window to leave room for prompts
            if context_window_size is not None:
                max_tokens = min(
                    int(
                        get_setting_from_snapshot(
                            "llm.max_tokens",
                            100000,
                            settings_snapshot=settings_snapshot,
                        )
                    ),
                    int(context_window_size * 0.8),
                )
                ollama_params["max_tokens"] = max_tokens

        llm = ChatOllama(**ollama_params)

        # Log the actual client configuration after creation
        logger.debug(
            f"ChatOllama created - base_url attribute: {getattr(llm, 'base_url', 'not found')}"
        )

        return llm

    @classmethod
    def is_available(cls, settings_snapshot=None):
        """Check if Ollama is running.

        Args:
            settings_snapshot: Optional settings snapshot to use

        Returns:
            True if Ollama is available, False otherwise
        """
        try:
            raw_base_url = get_setting_from_snapshot(
                "llm.ollama.url",
                None,
                settings_snapshot=settings_snapshot,
            )
            if not raw_base_url:
                logger.debug("Ollama URL not configured")
                return False
            base_url = normalize_url(raw_base_url)
            logger.info(f"Checking Ollama availability at {base_url}/api/tags")

            # Get authentication headers
            headers = cls._get_auth_headers(settings_snapshot=settings_snapshot)

            try:
                response = requests.get(
                    f"{base_url}/api/tags", timeout=3.0, headers=headers
                )
                if response.status_code == 200:
                    logger.info(
                        f"Ollama is available. Status code: {response.status_code}"
                    )
                    # Log first 100 chars of response to debug
                    logger.info(f"Response preview: {str(response.text)[:100]}")
                    return True
                else:
                    logger.warning(
                        f"Ollama API returned status code: {response.status_code}"
                    )
                    return False
            except requests.exceptions.RequestException as req_error:
                logger.exception(
                    f"Request error when checking Ollama: {req_error!s}"
                )
                return False
            except Exception:
                logger.exception("Unexpected error when checking Ollama")
                return False
        except Exception:
            logger.exception("Error in is_ollama_available")
            return False


# Keep the standalone functions for backward compatibility and registration
def create_ollama_llm(model_name=None, temperature=0.7, **kwargs):
    """Factory function for Ollama LLMs.

    Args:
        model_name: Name of the model to use (e.g., "llama2", "gemma", etc.)
        temperature: Model temperature (0.0-1.0)
        **kwargs: Additional arguments including settings_snapshot

    Returns:
        A configured ChatOllama instance

    Raises:
        ValueError: If Ollama is not available
    """
    return OllamaProvider.create_llm(model_name, temperature, **kwargs)


def is_ollama_available(settings_snapshot=None):
    """Check if Ollama is available.

    Args:
        settings_snapshot: Optional settings snapshot to use

    Returns:
        True if Ollama is running, False otherwise
    """
    return OllamaProvider.is_available(settings_snapshot)


def register_ollama_provider():
    """Register the Ollama provider with the LLM registry."""
    register_llm("ollama", create_ollama_llm)
    logger.info("Registered Ollama LLM provider")
