"""OpenAI embedding provider."""

from typing import Any, Dict, List, Optional

from langchain_core.embeddings import Embeddings
from loguru import logger

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


class OpenAIEmbeddingsProvider(BaseEmbeddingProvider):
    """
    OpenAI embedding provider.

    Targets the OpenAI cloud API by default, and any OpenAI-compatible
    endpoint (LM Studio, vLLM, llama.cpp server, etc.) when
    ``embeddings.openai.base_url`` is configured. An API key is required
    for the cloud, but optional for keyless local servers — the
    ``base_url``-set, ``api_key``-empty configuration falls back to a
    placeholder key so the OpenAI client request still goes out.
    """

    provider_name = "OpenAI"
    provider_key = "OPENAI"
    # An API key is required for the OpenAI cloud, but optional for
    # keyless OpenAI-compatible local servers; we keep the class-level
    # flag for callers that gate UI prompts on it, and handle the
    # base_url-set / key-empty case explicitly in ``is_available`` and
    # ``create_embeddings``.
    requires_api_key = True
    supports_local = False
    default_model = "text-embedding-3-small"  # type: ignore[assignment]
    # Placeholder key used when targeting an OpenAI-compatible local
    # server (api_key empty, base_url set). Mirrors the LLM-side
    # LMStudio provider's keyless-fallback pattern.
    _PLACEHOLDER_API_KEY = "lm-studio"

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

        Args:
            model: Model name (defaults to text-embedding-3-small)
            settings_snapshot: Optional settings snapshot
            **kwargs: Additional parameters (api_key, etc.)

        Returns:
            OpenAIEmbeddings instance

        Raises:
            ValueError: If API key is not configured
        """
        from langchain_openai import OpenAIEmbeddings

        # Get API key + base_url. Read base_url first so we can decide
        # whether a missing api_key is fatal (cloud) or just a keyless
        # local-server signal (OpenAI-compatible endpoint).
        base_url = kwargs.get("base_url")
        if base_url is None:
            base_url = get_setting_from_snapshot(
                "embeddings.openai.base_url",
                default=None,
                settings_snapshot=settings_snapshot,
            )

        api_key = kwargs.get("api_key")
        if api_key is None:
            api_key = get_setting_from_snapshot(
                "embeddings.openai.api_key",
                default=None,
                settings_snapshot=settings_snapshot,
            )

        if not api_key:
            if base_url:
                # OpenAI-compatible local server (LM Studio, vLLM,
                # llama.cpp). The server ignores the key but the
                # OpenAI client requires the field to be non-empty.
                logger.info(
                    "OpenAI embeddings: no API key set but base_url={} "
                    "is configured — using placeholder key for the "
                    "OpenAI-compatible endpoint.",
                    base_url,
                )
                api_key = cls._PLACEHOLDER_API_KEY
            else:
                logger.error("OpenAI API key not found in settings")
                raise ValueError(
                    "OpenAI API key not configured. "
                    "Please set embeddings.openai.api_key in settings, "
                    "or set embeddings.openai.base_url to point at an "
                    "OpenAI-compatible local server."
                )

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

        dimensions = kwargs.get("dimensions")
        if dimensions is None:
            dimensions = get_setting_from_snapshot(
                "embeddings.openai.dimensions",
                default=None,
                settings_snapshot=settings_snapshot,
            )

        logger.info(f"Creating OpenAIEmbeddings with model={model}")

        # Build parameters. Annotated as Dict[str, Any] so the
        # heterogeneous values (str for model/key/base_url, int for
        # dimensions) and the **params unpack into OpenAIEmbeddings
        # type-check under mypy.
        params: Dict[str, Any] = {
            "model": model,
            "openai_api_key": api_key,
        }

        if base_url:
            params["openai_api_base"] = base_url

        # For text-embedding-3 models, dimensions can be customized
        if dimensions and model.startswith("text-embedding-3"):
            params["dimensions"] = int(dimensions)

        return OpenAIEmbeddings(**params)

    @classmethod
    def is_available(
        cls, settings_snapshot: Optional[Dict[str, Any]] = None
    ) -> bool:
        """Check if OpenAI embeddings are available.

        Available when either an API key (cloud) or a custom base URL
        (OpenAI-compatible local server) is configured. A blank
        installation still reports unavailable so the UI doesn't list
        the provider on first launch.
        """
        try:
            api_key = get_setting_from_snapshot(
                "embeddings.openai.api_key",
                default=None,
                settings_snapshot=settings_snapshot,
            )
            if api_key and str(api_key).strip():
                return True
            base_url = get_setting_from_snapshot(
                "embeddings.openai.base_url",
                default=None,
                settings_snapshot=settings_snapshot,
            )
            return bool(base_url and str(base_url).strip())
        except Exception:
            logger.debug(
                "Error checking OpenAI embedding availability", exc_info=True
            )
            return False

    @classmethod
    def get_available_models(
        cls, settings_snapshot: Optional[Dict[str, Any]] = None
    ) -> List[Dict[str, str]]:
        """Get list of available OpenAI embedding models from API."""
        try:
            from openai import OpenAI

            api_key = get_setting_from_snapshot(
                "embeddings.openai.api_key",
                default=None,
                settings_snapshot=settings_snapshot,
            )
            base_url = get_setting_from_snapshot(
                "embeddings.openai.base_url",
                default=None,
                settings_snapshot=settings_snapshot,
            )

            if not api_key:
                if base_url:
                    # Keyless OpenAI-compatible local server — use a
                    # placeholder so the client request can proceed.
                    api_key = cls._PLACEHOLDER_API_KEY
                else:
                    logger.warning("OpenAI API key not configured")
                    return []

            client_kwargs: Dict[str, Any] = {"api_key": api_key}
            if base_url:
                client_kwargs["base_url"] = base_url
            client = OpenAI(**client_kwargs)
            models_response = client.models.list()

            # Filter for embedding models only
            embedding_models = []
            for model in models_response.data:
                model_id = model.id
                # OpenAI embedding models typically have "embedding" in the name
                if "embedding" in model_id.lower():
                    embedding_models.append(
                        {
                            "value": model_id,
                            "label": model_id,
                        }
                    )

            return embedding_models

        except Exception:
            logger.exception("Error fetching OpenAI embedding models")
            return []
