"""
Central configuration for embedding providers.

This module provides the main get_embeddings() function and availability checks
for different embedding providers, similar to llm_config.py.
"""

from typing import Any, Dict, Optional, Type

from langchain_core.embeddings import Embeddings
from loguru import logger

from ..config.thread_settings import get_setting_from_snapshot
from .providers.base import BaseEmbeddingProvider

# Valid embedding provider options
VALID_EMBEDDING_PROVIDERS = [
    "sentence_transformers",
    "ollama",
    "openai",
]

# Lazy-loaded provider classes dict
_PROVIDER_CLASSES: Optional[Dict[str, Type[BaseEmbeddingProvider]]] = None


def _get_provider_classes() -> Dict[str, Type[BaseEmbeddingProvider]]:
    """Lazy load provider classes to avoid circular imports."""
    global _PROVIDER_CLASSES
    if _PROVIDER_CLASSES is None:
        from .providers.implementations.sentence_transformers import (
            SentenceTransformersProvider,
        )
        from .providers.implementations.ollama import OllamaEmbeddingsProvider
        from .providers.implementations.openai import OpenAIEmbeddingsProvider

        _PROVIDER_CLASSES = {
            "sentence_transformers": SentenceTransformersProvider,
            "ollama": OllamaEmbeddingsProvider,
            "openai": OpenAIEmbeddingsProvider,
        }
    return _PROVIDER_CLASSES


def is_sentence_transformers_available() -> bool:
    """Check if Sentence Transformers is available."""
    provider_classes = _get_provider_classes()
    return provider_classes["sentence_transformers"].is_available()


def is_ollama_embeddings_available(
    settings_snapshot: Optional[Dict[str, Any]] = None,
) -> bool:
    """Check if Ollama embeddings are available."""
    provider_classes = _get_provider_classes()
    return provider_classes["ollama"].is_available(settings_snapshot)


def is_openai_embeddings_available(
    settings_snapshot: Optional[Dict[str, Any]] = None,
) -> bool:
    """Check if OpenAI embeddings are available."""
    provider_classes = _get_provider_classes()
    return provider_classes["openai"].is_available(settings_snapshot)


def get_available_embedding_providers(
    settings_snapshot: Optional[Dict[str, Any]] = None,
) -> Dict[str, str]:
    """
    Return available embedding providers.

    Args:
        settings_snapshot: Optional settings snapshot

    Returns:
        Dict mapping provider keys to display names
    """
    providers = {}

    if is_sentence_transformers_available():
        providers["sentence_transformers"] = "Sentence Transformers (Local)"

    if is_ollama_embeddings_available(settings_snapshot):
        providers["ollama"] = "Ollama (Local)"

    if is_openai_embeddings_available(settings_snapshot):
        # Single entry covers the OpenAI cloud API and any
        # OpenAI-compatible endpoint (LM Studio, vLLM, llama.cpp);
        # the provider class branches on
        # ``embeddings.openai.base_url`` at runtime.
        providers["openai"] = "OpenAI / OpenAI-Compatible Endpoint"

    return providers


def get_embedding_function(
    provider: Optional[str] = None,
    model_name: Optional[str] = None,
    settings_snapshot: Optional[Dict[str, Any]] = None,
    **kwargs,
):
    """
    Get a callable embedding function that can embed texts.

    Args:
        provider: Embedding provider to use
        model_name: Model name to use
        settings_snapshot: Optional settings snapshot
        **kwargs: Additional provider-specific parameters

    Returns:
        A callable that takes a list of texts and returns embeddings
    """
    embeddings = get_embeddings(
        provider=provider,
        model=model_name,
        settings_snapshot=settings_snapshot,
        **kwargs,
    )
    return embeddings.embed_documents


def get_embeddings(
    provider: Optional[str] = None,
    model: Optional[str] = None,
    settings_snapshot: Optional[Dict[str, Any]] = None,
    **kwargs,
) -> Embeddings:
    """
    Get embeddings instance based on provider and model.

    Args:
        provider: Embedding provider to use (if None, uses settings)
        model: Model name to use (if None, uses settings or provider default)
        settings_snapshot: Optional settings snapshot for thread-safe access
        **kwargs: Additional provider-specific parameters

    Returns:
        A LangChain Embeddings instance

    Raises:
        ValueError: If provider is invalid or not available
        ImportError: If required dependencies are not installed
    """
    # Get provider from settings if not specified
    if provider is None:
        provider = get_setting_from_snapshot(
            "embeddings.provider",
            default="sentence_transformers",
            settings_snapshot=settings_snapshot,
        )

    # Clean and normalize provider
    if provider:
        provider = provider.strip().strip("\"'").strip().lower()

    # Validate provider
    if provider not in VALID_EMBEDDING_PROVIDERS:
        logger.error(f"Invalid embedding provider: {provider}")
        raise ValueError(
            f"Invalid embedding provider: {provider}. "
            f"Must be one of: {VALID_EMBEDDING_PROVIDERS}"
        )

    logger.info(f"Getting embeddings with provider: {provider}, model: {model}")

    # Get provider class and create embeddings
    provider_classes = _get_provider_classes()
    provider_class = provider_classes.get(provider)

    if not provider_class:
        raise ValueError(f"Unsupported embedding provider: {provider}")

    return provider_class.create_embeddings(
        model=model, settings_snapshot=settings_snapshot, **kwargs
    )
