"""OpenAI connector (Chat Completions, with a Responses-API fallback).

Used directly for OpenAI, and — via a per-provider ``base_url`` — for every
provider that speaks the same protocol: DeepSeek, Ollama, xAI Grok, Alibaba
Qwen and Moonshot Kimi. Reasoning models (GPT-5.x, o-series, deepseek-reasoner)
reject ``temperature``, so it is only sent when explicitly requested; output
caps use the model's ``max_tokens_param``. A few OpenAI models (e.g. the *-pro*
and *-codex* tiers) are served only by the Responses API — those route through
``responses.create`` when ``spec.responses_api`` is set.
"""

from __future__ import annotations

from typing import Any

from pentestgpt_legacy.llm.base import BaseProvider, Message
from pentestgpt_legacy.llm.registry import ModelSpec


class OpenAICompatibleProvider(BaseProvider):
    """Async OpenAI-compatible provider (Chat Completions, optional Responses API)."""

    async def acomplete(
        self,
        messages: list[Message],
        system: str | None,
        spec: ModelSpec,
        *,
        max_output_tokens: int | None = None,
        temperature: float | None = None,
    ) -> str:
        try:
            from openai import AsyncOpenAI
        except ImportError as exc:  # pragma: no cover - install-time guard
            raise RuntimeError(
                "The 'openai' package is required. Install with: uv add openai"
            ) from exc

        # Ollama needs a non-empty key string but ignores its value.
        api_key = self.api_key or "not-needed"
        async with AsyncOpenAI(api_key=api_key, base_url=self.base_url) as client:
            if spec.responses_api:
                return await self._via_responses(
                    client, messages, system, spec, max_output_tokens, temperature
                )
            return await self._via_chat(
                client, messages, system, spec, max_output_tokens, temperature
            )

    @staticmethod
    async def _via_chat(
        client: Any,
        messages: list[Message],
        system: str | None,
        spec: ModelSpec,
        max_output_tokens: int | None,
        temperature: float | None,
    ) -> str:
        wire_messages: list[dict[str, Any]] = []
        if system:
            wire_messages.append({"role": "system", "content": system})
        wire_messages.extend(messages)

        kwargs: dict[str, Any] = {"model": spec.api_id, "messages": wire_messages}
        if temperature is not None:
            kwargs["temperature"] = temperature
        if max_output_tokens is not None:
            kwargs[spec.max_tokens_param] = max_output_tokens

        response = await client.chat.completions.create(**kwargs)
        content = response.choices[0].message.content
        return content or ""

    @staticmethod
    async def _via_responses(
        client: Any,
        messages: list[Message],
        system: str | None,
        spec: ModelSpec,
        max_output_tokens: int | None,
        temperature: float | None,
    ) -> str:
        kwargs: dict[str, Any] = {"model": spec.api_id, "input": list(messages)}
        if system:
            kwargs["instructions"] = system
        if temperature is not None:
            kwargs["temperature"] = temperature
        if max_output_tokens is not None:
            kwargs["max_output_tokens"] = max_output_tokens

        response = await client.responses.create(**kwargs)
        return response.output_text or ""
