"""Base OpenAI-compatible endpoint provider for Local Deep Research."""

from langchain_openai import ChatOpenAI
from loguru import logger

from ...config.thread_settings import (
    get_setting_from_snapshot,
    NoSettingsContextError,
)
from ...utilities.url_utils import normalize_url
from .base import BaseLLMProvider


class OpenAICompatibleProvider(BaseLLMProvider):
    """Base class for OpenAI-compatible API providers.

    This class provides a common implementation for any service that offers
    an OpenAI-compatible API endpoint (Google, OpenRouter, Groq, Together, etc.)
    """

    # Override these in subclasses
    provider_name = "openai_endpoint"  # Name used in logs
    api_key_setting = "llm.openai_endpoint.api_key"  # Settings key for API key
    url_setting = None  # Settings key for URL (e.g., "llm.lmstudio.url")
    default_base_url = "https://api.openai.com/v1"  # Default endpoint URL
    default_model = (
        ""  # User must explicitly configure llm.model — no silent fallback
    )

    @classmethod
    def create_llm(cls, model_name=None, temperature=0.7, **kwargs):
        """Factory function for OpenAI-compatible 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 ChatOpenAI instance

        Raises:
            ValueError: If API key is not configured
        """
        settings_snapshot = kwargs.get("settings_snapshot")

        # Get API key from settings (if provider requires one)
        if cls.api_key_setting:
            api_key = get_setting_from_snapshot(
                cls.api_key_setting,
                default=None,
                settings_snapshot=settings_snapshot,
            )

            if not api_key:
                logger.error(
                    f"{cls.provider_name} API key not found in settings"
                )
                raise ValueError(
                    f"{cls.provider_name} API key not configured. "
                    f"Please set {cls.api_key_setting} in settings."
                )
        else:
            # Provider doesn't require API key (e.g., LM Studio)
            api_key = kwargs.get("api_key", "dummy-key")

        # Require an explicit model — no silent fallback to a hardcoded default.
        if not model_name or not model_name.strip():
            logger.error(f"{cls.provider_name} model name not provided")
            raise ValueError(
                f"{cls.provider_name} model not configured. "
                f"Please set llm.model in settings."
            )

        # Get endpoint URL (can be overridden in kwargs for flexibility)
        base_url = kwargs.get("base_url", cls.default_base_url)
        base_url = normalize_url(base_url) if base_url else cls.default_base_url

        # Build parameters for OpenAI client
        llm_params = {
            "model": model_name,
            "api_key": api_key,
            "base_url": base_url,
            "temperature": temperature,
        }

        # Add max_tokens if specified in settings
        try:
            max_tokens = get_setting_from_snapshot(
                "llm.max_tokens",
                default=None,
                settings_snapshot=settings_snapshot,
            )
            if max_tokens:
                llm_params["max_tokens"] = int(max_tokens)
        except NoSettingsContextError:
            pass  # Optional parameter

        # Add streaming if specified
        try:
            streaming = get_setting_from_snapshot(
                "llm.streaming",
                default=None,
                settings_snapshot=settings_snapshot,
            )
            if streaming is not None:
                llm_params["streaming"] = streaming
        except NoSettingsContextError:
            pass  # Optional parameter

        # Add max_retries if specified
        try:
            max_retries = get_setting_from_snapshot(
                "llm.max_retries",
                default=None,
                settings_snapshot=settings_snapshot,
            )
            if max_retries is not None:
                llm_params["max_retries"] = max_retries
        except NoSettingsContextError:
            pass  # Optional parameter

        # Add request_timeout if specified
        try:
            request_timeout = get_setting_from_snapshot(
                "llm.request_timeout",
                default=None,
                settings_snapshot=settings_snapshot,
            )
            if request_timeout is not None:
                llm_params["request_timeout"] = request_timeout
        except NoSettingsContextError:
            pass  # Optional parameter

        logger.info(
            f"Creating {cls.provider_name} LLM with model: {model_name}, "
            f"temperature: {temperature}, endpoint: {base_url}"
        )

        return ChatOpenAI(**llm_params)

    @classmethod
    def _create_llm_instance(cls, model_name=None, temperature=0.7, **kwargs):
        """Internal method to create LLM instance with provided parameters.

        This bypasses API key checking for providers that handle auth differently.
        """
        settings_snapshot = kwargs.get("settings_snapshot")

        # Require an explicit model — no silent fallback to a hardcoded default.
        if not model_name or not model_name.strip():
            logger.error(f"{cls.provider_name} model name not provided")
            raise ValueError(
                f"{cls.provider_name} model not configured. "
                f"Please set llm.model in settings."
            )

        # Get endpoint URL (can be overridden in kwargs for flexibility)
        base_url = kwargs.get("base_url", cls.default_base_url)
        base_url = normalize_url(base_url) if base_url else cls.default_base_url

        # Get API key from kwargs (caller is responsible for providing it)
        api_key = kwargs.get("api_key", "dummy-key")

        # Build parameters for OpenAI client
        llm_params = {
            "model": model_name,
            "api_key": api_key,
            "base_url": base_url,
            "temperature": temperature,
        }

        # Add optional parameters (same as in create_llm)
        try:
            max_tokens = get_setting_from_snapshot(
                "llm.max_tokens",
                default=None,
                settings_snapshot=settings_snapshot,
            )
            if max_tokens:
                llm_params["max_tokens"] = int(max_tokens)
        except NoSettingsContextError:
            pass

        return ChatOpenAI(**llm_params)

    @classmethod
    def is_available(cls, settings_snapshot=None):
        """Check if this provider is available.

        Args:
            settings_snapshot: Optional settings snapshot to use

        Returns:
            True if API key is configured (or not needed), False otherwise
        """
        try:
            # If provider doesn't require API key, it's available
            if not cls.api_key_setting:
                return True

            # Check if API key is configured
            api_key = get_setting_from_snapshot(
                cls.api_key_setting,
                default=None,
                settings_snapshot=settings_snapshot,
            )
            return bool(api_key and str(api_key).strip())
        except Exception:
            return False

    @classmethod
    def requires_auth_for_models(cls):
        """Check if this provider requires authentication for listing models.

        Override in subclasses that don't require auth.

        Returns:
            True if authentication is required, False otherwise
        """
        return True

    # Resolves base URL from settings; called by list_models().
    @classmethod
    def _get_base_url_for_models(cls, settings_snapshot=None):
        """Get the base URL to use for listing models.

        Reads from url_setting if defined, otherwise uses default_base_url.

        Args:
            settings_snapshot: Optional settings snapshot dict

        Returns:
            The base URL string to use for model listing
        """
        if cls.url_setting:
            # Use get_setting_from_snapshot which handles both settings_snapshot
            # and thread-local context, with proper fallback
            url = get_setting_from_snapshot(
                cls.url_setting,
                default=None,
                settings_snapshot=settings_snapshot,
            )
            if url:
                return url.rstrip("/")

        return cls.default_base_url

    @classmethod
    def list_models_for_api(cls, api_key=None, base_url=None):
        """List available models for API endpoint use.

        This method is designed to be called from Flask routes.

        Args:
            api_key: Optional API key (if None and required, returns empty list)
            base_url: Optional base URL to use (if None, uses cls.default_base_url)

        Returns:
            List of model dictionaries with 'value' and 'label' keys
        """
        try:
            # Defense-in-depth: never send a non-string credential to the SDK.
            # The OpenAI client coerces the api_key into "Authorization: Bearer
            # <repr(api_key)>" — passing a dict would leak its contents to the
            # endpoint we're listing models from.
            if api_key is not None and not isinstance(api_key, str):
                logger.error(
                    f"{cls.provider_name}.list_models_for_api received "
                    f"non-string api_key of type {type(api_key).__name__}; "
                    f"refusing to send."
                )
                return []

            # Check if auth is required
            if cls.requires_auth_for_models():
                if not api_key:
                    logger.debug(
                        f"{cls.provider_name} requires API key for model listing"
                    )
                    return []
            else:
                # Use a dummy key for providers that don't require auth
                api_key = api_key or "dummy-key-for-models-list"

            from openai import OpenAI

            # Use provided base_url or fall back to class default
            if not base_url:
                base_url = cls.default_base_url

            # Create OpenAI client (uses library defaults for timeout)
            client = OpenAI(api_key=api_key, base_url=base_url)

            # Fetch models
            logger.debug(
                f"Fetching models from {cls.provider_name} at {base_url}"
            )
            models_response = client.models.list()

            models = []
            for model in models_response.data:
                if model.id:
                    models.append(
                        {
                            "value": model.id,
                            "label": model.id,
                        }
                    )

            logger.info(f"Found {len(models)} models from {cls.provider_name}")
            return models

        except Exception:
            # Use warning level since connection failures are expected
            # when the provider is not running (e.g., LM Studio not started)
            logger.warning(f"Could not list models from {cls.provider_name}")
            return []

    # High-level settings-aware wrapper around list_models_for_api().
    # Documented in docs/developing/EXTENDING.md as the provider interface
    # for custom providers.
    @classmethod
    def list_models(cls, settings_snapshot=None):
        """List available models from this provider.

        Args:
            settings_snapshot: Optional settings snapshot to use

        Returns:
            List of model dictionaries with 'value' and 'label' keys
        """
        try:
            # Get API key from settings if auth is required
            api_key = None
            if cls.requires_auth_for_models():
                api_key = get_setting_from_snapshot(
                    cls.api_key_setting,
                    default=None,
                    settings_snapshot=settings_snapshot,
                )

            # Get base URL from settings if provider has configurable URL
            base_url = cls._get_base_url_for_models(settings_snapshot)

            return cls.list_models_for_api(api_key, base_url)

        except Exception:
            logger.exception(f"Error listing models from {cls.provider_name}")
            return []
