"""OpenAI-compatible provider adapter for local LLMs.

For Ollama, LM Studio, and any provider that implements the
OpenAI chat completions API with standard parameters.

Uses max_tokens (not max_completion_tokens) and always sends
temperature, matching the standard OpenAI-compatible API contract.

For OpenAI's native API (gpt-5-mini, o1, o3), use OpenAIProvider.
"""

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 OpenAICompatProvider:
    """OpenAI-compatible API provider for local LLMs."""

    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.ollama_model", ""))
        if not self._model:
            raise ExtractionError(
                "Ollama model not configured. Pass model= or set "
                "OLLAMA_MODEL environment variable."
            )

        self._defaults = defaults or CompletionParams()

        effective_base_url = (
            base_url or str(config.get("extraction.ollama_base_url", "")) or None
        )

        import openai

        # Local providers don't need real API keys but the SDK
        # requires a non-empty string at construction time
        effective_key = api_key
        if not api_key and effective_base_url:
            effective_key = "ollama"

        try:
            self._client = openai.AsyncOpenAI(
                api_key=effective_key,
                base_url=effective_base_url,
                max_retries=max_retries,
                timeout=timeout,
            )
        except openai.OpenAIError as exc:
            raise ExtractionError(
                f"Failed to initialize OpenAI-compatible 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-compatible API parameters.

        Uses max_tokens and always includes temperature,
        matching the standard OpenAI-compatible API contract.
        """
        return {
            "model": model,
            "messages": [{"role": m.role, "content": m.content} for m in messages],
            "max_tokens": max_tokens,
            "temperature": temperature,
        }

    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-compatible API ({self._model}). "
                    "If using a reasoning model (e.g. Qwen3), 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-compatible API error for model %s", self._model)
            raise ExtractionError(
                f"OpenAI-compatible API error ({self._model}): {exc}"
            ) from exc
        except Exception as exc:
            logger.exception(
                "OpenAI-compatible API call failed for model %s",
                self._model,
            )
            raise ExtractionError(
                f"OpenAI-compatible API call failed ({self._model}): {exc}"
            ) from exc
