"""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
from ....utilities.url_utils import normalize_url
from ....security import safe_get
from ..base import BaseLLMProvider


class OllamaProvider(BaseLLMProvider):
    """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
    url_setting = "llm.ollama.url"  # URL setting for model listing

    # Metadata for auto-discovery
    provider_key = "OLLAMA"
    company_name = "Ollama"
    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, base_url=None):
        """Get available models from Ollama.

        Args:
            api_key: Optional API key for authentication
            base_url: Base URL for Ollama API (required)

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

        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 = safe_get(
                f"{base_url}/api/tags",
                timeout=3,
                headers=headers,
                allow_localhost=True,
                allow_private_ips=True,
            )
            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(  # noqa: TRY301 — re-raised by except ValueError below
                        f"Model '{model_name}' not found in Ollama"
                    )
        except ValueError:
            raise
        except Exception:
            logger.debug(
                f"Error checking for model '{model_name}' in Ollama",
                exc_info=True,
            )
            # 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 = safe_get(
                    f"{base_url}/api/tags",
                    timeout=3,
                    headers=headers,
                    allow_localhost=True,
                    allow_private_ips=True,
                )
                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
                logger.warning(
                    f"Ollama API returned status code: {response.status_code}"
                )
                return False
            except requests.exceptions.RequestException:
                logger.warning("Request error when checking Ollama")
                return False
            except Exception:
                logger.warning("Unexpected error when checking Ollama")
                return False
        except Exception:
            logger.warning("Error in OllamaProvider.is_available")
            return False

    @classmethod
    def requires_auth_for_models(cls):
        """Ollama is local and does not need auth to list models."""
        return False
