"""OpenAI LLM provider for Local Deep Research."""

from langchain_openai import ChatOpenAI
from loguru import logger

# get_setting_from_snapshot and NoSettingsContextError are imported inside
# create_llm() so test patches at the source module
# (`local_deep_research.config.thread_settings`) are picked up by the
# function-local import at call time. Module-level binding here would be
# unaffected by the source-module patch.
from ....security.ssrf_validator import assert_base_url_safe
from ..openai_base import OpenAICompatibleProvider


class OpenAIProvider(OpenAICompatibleProvider):
    """OpenAI provider for Local Deep Research.

    This is the official OpenAI API provider.
    """

    provider_name = "OpenAI"
    api_key_setting = "llm.openai.api_key"
    default_model = ""  # User must explicitly pick a model — no silent fallback
    default_base_url = "https://api.openai.com/v1"

    # Metadata for auto-discovery
    provider_key = "OPENAI"
    company_name = "OpenAI"
    is_cloud = True

    @classmethod
    def create_llm(cls, model_name=None, temperature=0.7, **kwargs):
        """Factory function for OpenAI 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
        """
        from ....config.thread_settings import (
            _get_optional_setting,
            get_setting_from_snapshot,
            NoSettingsContextError,
        )

        settings_snapshot = kwargs.get("settings_snapshot")

        # resolve_api_key raises ValueError when the required key is missing
        # (preserves the legacy behavior with a unified error message).
        api_key = cls.resolve_api_key(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 (e.g. 'gpt-4o-mini')."
            )

        # Build OpenAI-specific parameters
        openai_params = {
            "model": model_name,
            "api_key": api_key,
            "temperature": temperature,
        }

        # Add optional parameters if they exist in settings
        try:
            api_base = get_setting_from_snapshot(
                "llm.openai.api_base",
                default=None,
                settings_snapshot=settings_snapshot,
            )
            if api_base:
                # SSRF guard for operator-configurable api_base. OpenAIProvider
                # has url_setting = None and overrides create_llm without
                # calling super, so the base-class guard never runs here.
                # ChatOpenAI uses its own httpx transport that bypasses
                # safe_requests, so an attacker who can edit llm.openai.api_base
                # could route inference at internal/cloud-credential endpoints.
                # Let ValueError propagate (fail-fast) — at inference
                # construction we fail rather than silently route traffic to a
                # metadata endpoint, matching ollama.create_llm.
                api_base = assert_base_url_safe(
                    api_base, setting_key="llm.openai.api_base"
                )
                openai_params["openai_api_base"] = api_base
        except NoSettingsContextError:
            pass  # Optional parameter

        # organization uses the falsy check intentionally — an empty string
        # must be dropped rather than forwarded to the OpenAI client.
        _get_optional_setting(
            openai_params,
            "openai_organization",
            "llm.openai.organization",
            settings_snapshot,
            check="falsy",
        )

        _get_optional_setting(
            openai_params,
            "streaming",
            "llm.streaming",
            settings_snapshot,
        )

        _get_optional_setting(
            openai_params,
            "max_retries",
            "llm.max_retries",
            settings_snapshot,
        )

        _get_optional_setting(
            openai_params,
            "request_timeout",
            "llm.request_timeout",
            settings_snapshot,
        )

        # Apply context-window-aware max_tokens cap (was previously only
        # applied in dead code in llm_config.get_llm).
        from .._helpers import (
            compute_max_tokens,
            get_context_window_for_provider,
        )

        try:
            context_window_size = get_context_window_for_provider(
                "openai", settings_snapshot=settings_snapshot
            )
            max_tokens = compute_max_tokens(
                settings_snapshot=settings_snapshot,
                context_window_size=context_window_size,
            )
            if max_tokens:  # Treat 0 as unset (matches legacy behavior)
                openai_params["max_tokens"] = max_tokens
        except NoSettingsContextError:
            pass  # Optional parameter

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

        return ChatOpenAI(**openai_params)
