"""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 ....security.ssrf_validator import assert_base_url_safe
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 = ""
    api_key_setting = "llm.ollama.api_key"  # Optional API key for authenticated Ollama instances
    api_key_optional = (
        True  # Bare Ollama needs no auth; key only matters behind a proxy
    )
    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 over settings)
            settings_snapshot: Optional settings snapshot to get API key from

        Returns:
            Dict of headers, empty if no API key configured
        """
        if api_key is not None:
            key = str(api_key).strip()
            return {"Authorization": f"Bearer {key}"} if key else {}
        return cls.build_bearer_header(settings_snapshot=settings_snapshot)

    @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)
        try:
            base_url = assert_base_url_safe(
                base_url, setting_key="llm.ollama.url"
            )
        except ValueError:
            logger.warning(
                "Ollama base_url failed SSRF validation; "
                "check llm.ollama.url config"
            )
            return []

        # 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")

        # Defense-in-depth: callers using the central get_llm() already get a
        # clear ValueError when llm.model is unset. This second check covers
        # direct callers of OllamaProvider.create_llm() (programmatic API,
        # custom registrations) so they don't get a confusing langchain
        # error about a model that wasn't actually requested.
        if not model_name or not model_name.strip():
            logger.error("Ollama model name not provided to create_llm()")
            raise ValueError(
                "Ollama model not configured. Please set llm.model in "
                "settings (e.g. 'llama3.1:8b', 'qwen2.5:14b')."
            )

        # 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)
        # SSRF guard — let ValueError propagate so the research-query
        # caller surfaces a clear config error instead of silently
        # routing inference traffic to a misconfigured target.
        base_url = assert_base_url_safe(base_url, setting_key="llm.ollama.url")

        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

        # Resolve the context window through the shared helper so num_ctx
        # always matches the context_limit reported for overflow detection
        # (wrap_llm_without_think_tags uses the same resolution). No
        # max_tokens kwarg: ChatOllama ignores it (its output-length control
        # is num_predict), so passing it would only feign a cap.
        from .._helpers import get_context_window_for_provider

        context_window_size = get_context_window_for_provider(
            "ollama", settings_snapshot=settings_snapshot
        )
        ollama_params["num_ctx"] = context_window_size

        # Thinking/reasoning support for models like deepseek-r1 / qwen2.5.
        # When True, the model performs reasoning and the reasoning content
        # is separated into additional_kwargs (and discarded by LDR). When
        # False, the model gives direct answers without thinking. This was
        # previously only applied in the dead procedural code at
        # llm_config.get_llm("ollama"); reproducing it here so the live
        # path honors the setting too.
        enable_thinking = get_setting_from_snapshot(
            "llm.ollama.enable_thinking",
            True,
            settings_snapshot=settings_snapshot,
        )
        if isinstance(enable_thinking, bool):
            ollama_params["reasoning"] = enable_thinking

        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)
            # Graceful UI degradation: if base_url is unsafe, return
            # False instead of raising. Surface it as "Ollama unavailable"
            # in the model-list UI rather than crashing the request.
            try:
                base_url = assert_base_url_safe(
                    base_url, setting_key="llm.ollama.url"
                )
            except ValueError:
                logger.warning(
                    "Ollama base_url failed SSRF validation; "
                    "check llm.ollama.url config"
                )
                return False
            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
