"""Ollama embedding provider."""

from typing import Any, Dict, List, Optional

from langchain_community.embeddings import OllamaEmbeddings
from langchain_core.embeddings import Embeddings
from loguru import logger

from ....config.thread_settings import get_setting_from_snapshot
from ....utilities.llm_utils import get_ollama_base_url
from ..base import BaseEmbeddingProvider
from ....security import safe_get, safe_post


class OllamaEmbeddingsProvider(BaseEmbeddingProvider):
    """
    Ollama embedding provider.

    Uses Ollama API for local embedding models.
    No API key required, runs locally.
    """

    provider_name = "Ollama"
    provider_key = "OLLAMA"
    requires_api_key = False
    supports_local = True
    default_model = "nomic-embed-text"  # type: ignore[assignment]

    @classmethod
    def create_embeddings(
        cls,
        model: Optional[str] = None,
        settings_snapshot: Optional[Dict[str, Any]] = None,
        **kwargs,
    ) -> Embeddings:
        """
        Create Ollama embeddings instance.

        Args:
            model: Model name (defaults to nomic-embed-text)
            settings_snapshot: Optional settings snapshot
            **kwargs: Additional parameters (base_url, etc.)

        Returns:
            OllamaEmbeddings instance
        """
        # Get model from settings if not specified
        if model is None:
            model = get_setting_from_snapshot(
                "embeddings.ollama.model",
                default=cls.default_model,
                settings_snapshot=settings_snapshot,
            )

        # Get Ollama URL
        base_url = kwargs.get("base_url")
        if base_url is None:
            base_url = get_ollama_base_url(settings_snapshot)

        # Without an explicit num_ctx, Ollama uses the model's modelfile
        # default (often 2048). Inputs longer than that return HTTP 500
        # ("input length exceeds the context length") rather than being
        # truncated, which aborts indexing mid-batch.
        num_ctx = get_setting_from_snapshot(
            "embeddings.ollama.num_ctx",
            default=8192,
            settings_snapshot=settings_snapshot,
        )

        logger.info(
            f"Creating OllamaEmbeddings with model={model}, "
            f"base_url={base_url}, num_ctx={num_ctx}"
        )

        ollama_kwargs: Dict[str, Any] = {
            "model": model,
            "base_url": base_url,
        }
        if num_ctx:
            ollama_kwargs["num_ctx"] = int(num_ctx)

        return OllamaEmbeddings(**ollama_kwargs)

    @classmethod
    def is_available(
        cls, settings_snapshot: Optional[Dict[str, Any]] = None
    ) -> bool:
        """Check if Ollama is available."""
        try:
            import requests

            # Get Ollama URL
            base_url = get_ollama_base_url(settings_snapshot)

            # Check if Ollama is running
            try:
                response = safe_get(
                    f"{base_url}/api/tags",
                    timeout=3,
                    allow_localhost=True,
                    allow_private_ips=True,
                )
                return response.status_code == 200
            except requests.exceptions.RequestException:
                return False

        except Exception:
            logger.exception("Error checking Ollama availability")
            return False

    @classmethod
    def _get_model_capabilities(
        cls, base_url: str, model_name: str
    ) -> Optional[List[str]]:
        """Query Ollama /api/show for a model's capabilities.

        Returns the capabilities list (e.g. ["embedding"]) or None on failure.
        """
        try:
            response = safe_post(
                f"{base_url}/api/show",
                json={"model": model_name},
                timeout=5,
                allow_localhost=True,
                allow_private_ips=True,
            )
            if response.status_code == 200:
                return response.json().get("capabilities")  # type: ignore[no-any-return]
        except Exception:
            logger.debug(f"Could not fetch capabilities for {model_name}")
        return None

    @classmethod
    def is_embedding_model(
        cls,
        model: str,
        settings_snapshot: Optional[Dict[str, Any]] = None,
    ) -> Optional[bool]:
        """Check whether an Ollama model supports embeddings.

        Uses the /api/show capabilities field. Falls back to name heuristics
        for older Ollama versions that don't expose capabilities.
        """
        base_url = get_ollama_base_url(settings_snapshot)
        caps = cls._get_model_capabilities(base_url, model)

        if caps is not None:
            return "embedding" in caps

        # Fallback: older Ollama without capabilities field
        return _name_looks_like_embedding(model)

    @classmethod
    def get_available_models(
        cls, settings_snapshot: Optional[Dict[str, Any]] = None
    ) -> List[Dict[str, str]]:
        """Get all Ollama models with embedding compatibility info.

        Queries each model's capabilities via /api/show and marks models
        with an `is_embedding` flag. Returns embedding models first,
        then non-embedding models.
        """
        from ....utilities.llm_utils import fetch_ollama_models

        base_url = get_ollama_base_url(settings_snapshot)
        all_models = fetch_ollama_models(base_url, timeout=3.0)

        if not all_models:
            return []

        embedding_models = []
        other_models = []

        for model in all_models:
            model_name = model["value"]
            caps = cls._get_model_capabilities(base_url, model_name)

            if caps is not None:
                is_embed = "embedding" in caps
            else:
                # Older Ollama without capabilities — use name heuristic
                is_embed = _name_looks_like_embedding(model_name)

            model["is_embedding"] = is_embed  # type: ignore[assignment]
            if is_embed:
                embedding_models.append(model)
            else:
                other_models.append(model)

        logger.info(
            f"Found {len(embedding_models)} embedding models and "
            f"{len(other_models)} other models from Ollama"
        )

        # Embedding models first, then the rest
        return embedding_models + other_models


def _name_looks_like_embedding(model_name: str) -> bool:
    """Heuristic: check if a model name suggests it's an embedding model.

    Used as fallback for Ollama versions that don't expose capabilities.
    """
    name_lower = model_name.lower()
    return "embed" in name_lower or "bge" in name_lower
