# utilities/llm_utils.py
"""
LLM utilities for Local Deep Research.

This module provides utility functions for working with language models
when the user's llm_config.py is missing or incomplete.
"""

from loguru import logger
from typing import Any, Optional, Dict

from ..config.thread_settings import get_setting_from_snapshot


def get_ollama_base_url(
    settings_snapshot: Optional[Dict[str, Any]] = None,
) -> str:
    """
    Get Ollama base URL from settings with normalization.

    Checks both embeddings.ollama.url and llm.ollama.url settings,
    falling back to http://localhost:11434.

    Args:
        settings_snapshot: Optional settings snapshot

    Returns:
        Normalized Ollama base URL
    """
    from .url_utils import normalize_url

    raw_base_url = get_setting_from_snapshot(
        "embeddings.ollama.url",
        default=get_setting_from_snapshot(
            "llm.ollama.url",  # Fall back to LLM setting
            default="http://localhost:11434",
            settings_snapshot=settings_snapshot,
        ),
        settings_snapshot=settings_snapshot,
    )
    return (
        normalize_url(raw_base_url)
        if raw_base_url
        else "http://localhost:11434"
    )


def get_server_url(settings_snapshot: Optional[Dict[str, Any]] = None) -> str:
    """
    Get server URL from settings with fallback logic.

    Checks multiple sources in order:
    1. Direct server_url in settings snapshot
    2. system.server_url in settings
    3. Constructs from web.host, web.port, and web.use_https
    4. Fallback to http://127.0.0.1:5000/

    Args:
        settings_snapshot: Optional settings snapshot

    Returns:
        Server URL with trailing slash
    """
    from loguru import logger

    server_url = None

    if settings_snapshot:
        # Try to get server URL from research metadata first (where we added it)
        server_url = settings_snapshot.get("server_url")

        # If not found, try system settings
        if not server_url:
            system_settings = settings_snapshot.get("system", {})
            server_url = system_settings.get("server_url")

        # If not found, try web.host and web.port settings
        if not server_url:
            host = get_setting_from_snapshot(
                "web.host", settings_snapshot, "127.0.0.1"
            )
            port = get_setting_from_snapshot(
                "web.port", settings_snapshot, 5000
            )
            use_https = get_setting_from_snapshot(
                "web.use_https", settings_snapshot, True
            )

            # Use localhost for 0.0.0.0 bindings as that's what users will use
            if host == "0.0.0.0":
                host = "127.0.0.1"

            scheme = "https" if use_https else "http"
            server_url = f"{scheme}://{host}:{port}/"

    # Fallback to default if still not found
    if not server_url:
        server_url = "http://127.0.0.1:5000/"
        logger.warning("Could not determine server URL, using default")

    return server_url


def fetch_ollama_models(
    base_url: str,
    timeout: float = 3.0,
    auth_headers: Optional[Dict[str, str]] = None,
) -> list[Dict[str, str]]:
    """
    Fetch available models from Ollama API.

    Centralized function to avoid duplication between LLM and embedding providers.

    Args:
        base_url: Ollama base URL (should be normalized)
        timeout: Request timeout in seconds
        auth_headers: Optional authentication headers

    Returns:
        List of model dicts with 'value' (model name) and 'label' (display name) keys.
        Returns empty list on error.
    """
    from loguru import logger
    from ..security import safe_get

    models = []

    try:
        response = safe_get(
            f"{base_url}/api/tags",
            timeout=timeout,
            headers=auth_headers or {},
            allow_localhost=True,
            allow_private_ips=True,
        )

        if response.status_code == 200:
            data = response.json()

            # Handle both newer and older Ollama API formats
            ollama_models = (
                data.get("models", []) if isinstance(data, dict) else data
            )

            for model_data in ollama_models:
                model_name = model_data.get("name", "")
                if model_name:
                    models.append({"value": model_name, "label": model_name})

            logger.info(f"Found {len(models)} Ollama models")
        else:
            logger.warning(
                f"Failed to fetch Ollama models: HTTP {response.status_code}"
            )

    except Exception:
        logger.exception("Error fetching Ollama models")

    return models


def get_model(
    model_name: Optional[str] = None,
    model_type: Optional[str] = None,
    temperature: Optional[float] = None,
    **kwargs,
) -> Any:
    """
    Get a language model instance as fallback when llm_config.get_llm is not available.

    Args:
        model_name: Name of the model to use
        model_type: Type of the model provider
        temperature: Model temperature
        **kwargs: Additional parameters

    Returns:
        LangChain language model instance
    """
    # Get default values from kwargs or use reasonable defaults
    model_name = model_name or kwargs.get("DEFAULT_MODEL", "mistral")
    model_type = model_type or kwargs.get("DEFAULT_MODEL_TYPE", "ollama")
    temperature = temperature or kwargs.get("DEFAULT_TEMPERATURE", 0.7)
    max_tokens = kwargs.get("max_tokens", kwargs.get("MAX_TOKENS", 30000))

    # Common parameters
    common_params = {
        "temperature": temperature,
        "max_tokens": max_tokens,
    }

    # Add additional kwargs
    for key, value in kwargs.items():
        if key not in [
            "DEFAULT_MODEL",
            "DEFAULT_MODEL_TYPE",
            "DEFAULT_TEMPERATURE",
            "MAX_TOKENS",
        ]:
            common_params[key] = value

    # Try to load the model based on type
    if model_type == "ollama":
        try:
            from langchain_ollama import ChatOllama

            return ChatOllama(model=model_name, **common_params)
        except ImportError:
            try:
                from langchain_community.llms import Ollama

                return Ollama(model=model_name, **common_params)
            except ImportError:
                logger.exception(
                    "Neither langchain_ollama nor langchain_community.llms.Ollama available"
                )
                raise

    elif model_type == "openai":
        try:
            from langchain_openai import ChatOpenAI

            api_key = get_setting_from_snapshot("llm.openai.api_key")
            if not api_key:
                raise ValueError("OpenAI API key not found in settings")
            return ChatOpenAI(
                model=model_name, api_key=api_key, **common_params
            )
        except ImportError:
            logger.exception("langchain_openai not available")
            raise

    elif model_type == "anthropic":
        try:
            from langchain_anthropic import ChatAnthropic

            api_key = get_setting_from_snapshot("llm.anthropic.api_key")
            if not api_key:
                raise ValueError("Anthropic API key not found in settings")
            return ChatAnthropic(
                model=model_name, anthropic_api_key=api_key, **common_params
            )
        except ImportError:
            logger.exception("langchain_anthropic not available")
            raise

    elif model_type == "openai_endpoint":
        try:
            from langchain_openai import ChatOpenAI

            api_key = get_setting_from_snapshot("llm.openai_endpoint.api_key")
            if not api_key:
                raise ValueError(
                    "OpenAI endpoint API key not found in settings"
                )

            endpoint_url = kwargs.get(
                "OPENAI_ENDPOINT_URL",
                get_setting_from_snapshot(
                    "llm.openai_endpoint.url", "https://openrouter.ai/api/v1"
                ),
            )

            if model_name is None and not kwargs.get(
                "OPENAI_ENDPOINT_REQUIRES_MODEL", True
            ):
                return ChatOpenAI(
                    api_key=api_key,
                    openai_api_base=endpoint_url,
                    **common_params,
                )
            else:
                return ChatOpenAI(
                    model=model_name,
                    api_key=api_key,
                    openai_api_base=endpoint_url,
                    **common_params,
                )
        except ImportError:
            logger.exception("langchain_openai not available")
            raise

    # Default fallback
    try:
        from langchain_ollama import ChatOllama

        logger.warning(
            f"Unknown model type '{model_type}', defaulting to Ollama"
        )
        return ChatOllama(model=model_name, **common_params)
    except (ImportError, Exception):
        logger.exception("Failed to load any model")

        # Last resort: create a dummy model
        try:
            from langchain_community.llms.fake import FakeListLLM

            return FakeListLLM(
                responses=[
                    "No language models are available. Please install Ollama or set up API keys."
                ]
            )
        except ImportError:
            raise ValueError(
                "No language models available and could not create dummy model"
            )
