"""OpenAI native provider adapter.

For OpenAI's own API (gpt-5-mini, o1, o3, etc).
Uses max_completion_tokens (required by reasoning models).
Omits temperature when set to 0.0 (reasoning models reject it).

For local OpenAI-compatible providers (Ollama, LM Studio),
use OpenAICompatProvider instead.
"""

from __future__ import annotations

import logging
from typing import Any

from recollect.exceptions import ExtractionError
from recollect.llm.types import CompletionParams, Message

logger = logging.getLogger(__name__)


class OpenAIProvider:
    """OpenAI native API provider."""

    def __init__(
        self,
        model: str | None = None,
        api_key: str | None = None,
        base_url: str | None = None,
        defaults: CompletionParams | None = None,
        max_retries: int = 2,
        timeout: float = 60.0,
    ) -> None:
        from recollect.config import config

        self._model = model or str(config.get("extraction.openai_model", ""))
        if not self._model:
            raise ExtractionError(
                "OpenAI model not configured. Pass model= or set "
                "OPENAI_MODEL environment variable."
            )

        self._defaults = defaults or CompletionParams()

        effective_base_url = (
            base_url or str(config.get("extraction.openai_base_url", "")) or None
        )  # Empty string -> None (SDK default)

        import openai

        try:
            self._client = openai.AsyncOpenAI(
                api_key=api_key,  # SDK falls back to OPENAI_API_KEY env var
                base_url=effective_base_url,
                max_retries=max_retries,
                timeout=timeout,
            )
        except openai.OpenAIError as exc:
            raise ExtractionError(f"Failed to initialize OpenAI client: {exc}") from exc

    @property
    def model_name(self) -> str:
        return self._model

    @staticmethod
    def _build_params(
        model: str,
        messages: list[Message],
        max_tokens: int,
        temperature: float,
    ) -> dict[str, Any]:
        """Build OpenAI API parameters.

        Uses max_completion_tokens (required by reasoning models).
        Omits temperature when 0.0 (reasoning models reject it).
        """
        params: dict[str, Any] = {
            "model": model,
            "messages": [{"role": m.role, "content": m.content} for m in messages],
            "max_completion_tokens": max_tokens,
        }
        # Reasoning models (o1, o3, gpt-5-mini) reject temperature.
        # Only include it when explicitly set to non-zero.
        if temperature > 0.0:
            params["temperature"] = temperature
        return params

    async def complete(
        self,
        messages: list[Message],
        **kwargs: Any,
    ) -> str:
        import openai

        effective = {
            **self._defaults.model_dump(exclude_none=True),
            **kwargs,
        }
        max_tokens = int(effective.pop("max_tokens", 1024))
        temperature = float(effective.pop("temperature", 0.0))

        params = self._build_params(self._model, messages, max_tokens, temperature)
        params.update(effective)  # passthrough extra params

        try:
            response = await self._client.chat.completions.create(**params)
            content = response.choices[0].message.content
            if content is None:
                raise ExtractionError(
                    f"Empty response from OpenAI API ({self._model}). "
                    "If using a reasoning model, internal thinking tokens "
                    "may have consumed the entire max_tokens budget. "
                    "Try increasing max_tokens."
                )
            return str(content).strip()
        except ExtractionError:
            raise
        except openai.APIError as exc:
            logger.exception("OpenAI API error for model %s", self._model)
            raise ExtractionError(f"OpenAI API error ({self._model}): {exc}") from exc
        except Exception as exc:
            logger.exception("OpenAI API call failed for model %s", self._model)
            raise ExtractionError(
                f"OpenAI API call failed ({self._model}): {exc}"
            ) from exc
