# backend/app/llm/openai_compat.py
# OpenAI 호환 Chat Completions 엔드포인트용 프로바이더.
# OpenAI 본가뿐 아니라 OpenRouter·Groq·Together·vLLM·LM Studio·Gemini(OpenAI 호환 베이스) 등
# `/v1/chat/completions` + `/v1/models` 규약을 따르는 모든 엔드포인트를 host(base_url)로 받는다.
import httpx

from ..runtime_config import effective_llm
from .ollama import _coerce, _lenient_json
from .prompts import CLASSIFY_SYSTEM, build_classify_prompt
from .provider import Classification, LLMProvider


class OpenAICompatProvider(LLMProvider):
    name = "openai"

    def __init__(
        self,
        host: str | None = None,
        model: str | None = None,
        api_key: str | None = None,
        timeout: float | None = None,
    ):
        eff = effective_llm()
        # host = base_url(예: https://api.openai.com/v1). 끝 슬래시 정규화.
        self.base = (host or eff.host).rstrip("/")
        self.model = model or eff.model
        self.api_key = api_key if api_key is not None else eff.api_key
        self.timeout = timeout if timeout is not None else eff.timeout

    def _headers(self) -> dict:
        h = {"Content-Type": "application/json"}
        if self.api_key:
            h["Authorization"] = f"Bearer {self.api_key}"
        return h

    def _chat(self, messages: list[dict]) -> str:
        body = {
            "model": self.model,
            "messages": messages,
            "temperature": 0.2,
            "response_format": {"type": "json_object"},
        }
        try:
            r = httpx.post(
                f"{self.base}/chat/completions",
                json=body,
                headers=self._headers(),
                timeout=self.timeout,
            )
            r.raise_for_status()
        except httpx.HTTPStatusError as e:
            # 일부 호환 서버는 response_format 을 모름(400) → 빼고 1회 재시도.
            if e.response is not None and e.response.status_code == 400:
                body.pop("response_format", None)
                r = httpx.post(
                    f"{self.base}/chat/completions",
                    json=body,
                    headers=self._headers(),
                    timeout=self.timeout,
                )
                r.raise_for_status()
            else:
                raise
        return r.json()["choices"][0]["message"]["content"]

    def list_models(self) -> list[str]:
        try:
            r = httpx.get(f"{self.base}/models", headers=self._headers(), timeout=5.0)
            if r.status_code != 200:
                return []
            data = r.json().get("data", [])
            return [m.get("id", "") for m in data if m.get("id")]
        except Exception:
            return []

    def health(self) -> dict:
        try:
            r = httpx.get(f"{self.base}/models", headers=self._headers(), timeout=5.0)
            ok = r.status_code == 200
            return {
                "reachable": ok,
                "provider": self.name,
                "model": self.model,
                "host": self.base,
                "detail": "ok" if ok else f"HTTP {r.status_code}",
            }
        except Exception as e:
            return {
                "reachable": False,
                "provider": self.name,
                "model": self.model,
                "host": self.base,
                "detail": str(e),
            }

    def generate_json(self, prompt: str, schema: dict | None = None) -> dict:
        content = self._chat([{"role": "user", "content": prompt}])
        return _lenient_json(content)

    def classify_capture(self, raw: str, context: dict) -> Classification:
        prompt = build_classify_prompt(raw, context)
        content = self._chat(
            [
                {"role": "system", "content": CLASSIFY_SYSTEM},
                {"role": "user", "content": prompt},
            ]
        )
        return _coerce(_lenient_json(content), raw, context, model=f"openai:{self.model}")
