# #region LLMClient [C:3] [TYPE Class] [SEMANTICS llm, openai, api, retry]
# @BRIEF Call OpenAI-compatible LLM APIs with retry logic for rate limiting and structured output fallback.
# @SIDE_EFFECT Makes HTTP POST calls to external LLM API.
# @RELATION DEPENDS_ON -> [EXT:requests]

import os
import time as _time
from typing import Any

from ...core.logger import logger


# #region _get_verify [C:1] [TYPE Function] [SEMANTICS translate, ssl, verify]
# @BRIEF Resolve SSL verification path from LLM_SSL_VERIFY env var.
# @RATIONALE Используем capath=/etc/ssl/certs/ вместо cafile, потому что
#   OpenSSL 3.x не использует intermediate CA сертификаты из cafile для
#   построения цепочки (verify code 20). capath с хеш-симлинками работает
#   корректно (verify code 0).
# @REJECTED cafile отвергнут — OpenSSL 3.x не использует intermediate CA
#   из единого bundle-файла. Только capath с хеш-симлинками даёт code 0.
# @POST Returns path to /etc/ssl/certs/ when enabled, False when disabled.
def _get_verify() -> str | bool:
    raw = os.getenv("LLM_SSL_VERIFY", "true").strip().lower()
    if raw in ("false", "0", "no", "off"):
        return False
    return "/etc/ssl/certs/"
# #endregion _get_verify


class LLMClient:
    """Call OpenAI-compatible LLM APIs with retry and structured output handling."""

    @staticmethod
    def call_openai_compatible(
        base_url: str,
        api_key: str,
        model: str,
        prompt: str,
        provider_type: str = "openai",
        max_tokens: int = 8192,
        disable_reasoning: bool = False,
    ) -> str:
        """Call an OpenAI-compatible API for translation."""
        if not base_url:
            raise ValueError("LLM provider has no base_url configured")
        import requests as http_requests

        url = f"{base_url.rstrip('/')}/chat/completions"
        headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}

        system_content = (
            "You are a database content translation assistant. "
            "Translate the provided text accurately, preserving data semantics. "
            "Respond directly with ONLY the JSON result. "
            "Do NOT include any reasoning, thinking, chain-of-thought, analysis, "
            "or explanation. Output ONLY valid JSON."
        )

        payload: dict[str, Any] = {
            "model": model,
            "messages": [
                {"role": "system", "content": system_content},
                {"role": "user", "content": prompt},
            ],
            "temperature": 0.1,
            "max_tokens": max_tokens,
        }

        if provider_type in ("openai", "openai_compatible", "kilo", "openrouter", "litellm"):
            if not disable_reasoning:
                payload["response_format"] = {"type": "json_object"}

        if disable_reasoning:
            if provider_type not in ("kilo", "openrouter", "litellm"):
                payload["reasoning_effort"] = "none"
            payload["max_tokens"] = max_tokens

        logger.reason(f"LLM request url={base_url} model={payload.get('model')} "
                      f"provider_type={provider_type} prompt_len={len(prompt)}")

        _max_retry_429 = 3
        _retry_count_429 = 0
        while _retry_count_429 < _max_retry_429:
            response = http_requests.post(url, headers=headers, json=payload, timeout=600, verify=_get_verify())
            if response.status_code == 429:
                _retry_count_429 += 1
                retry_after = response.headers.get("Retry-After")
                wait = int(retry_after) if retry_after and retry_after.isdigit() else 2 ** _retry_count_429
                logger.explore(f"Rate limited (429), retry {_retry_count_429}/{_max_retry_429} after {wait}s")
                _time.sleep(wait)
                if _retry_count_429 >= _max_retry_429:
                    break
            else:
                break

        _response_format_error_patterns = ("response_format", "structured_outputs", "structured", "json_object")
        if (not response.ok and response.status_code == 400
                and any(p in (response.text or "").lower() for p in _response_format_error_patterns)):
            logger.explore("Structured outputs not supported, retrying without response_format")
            payload.pop("response_format", None)
            response = http_requests.post(url, headers=headers, json=payload, timeout=600, verify=_get_verify())

        if not response.ok:
            logger.explore(f"LLM API error status={response.status_code} model={payload.get('model')} body={response.text[:2000]}")

        response.raise_for_status()
        data = response.json()
        choices = data.get("choices", [])
        if not choices:
            raise ValueError("LLM returned no choices")

        try:
            msg = choices[0].get("message") or {}
            refusal = msg.get("refusal")
            if refusal:
                raise ValueError(f"LLM refused to respond: {refusal}")
            content = msg.get("content") or ""
        except TypeError as e:
            raise ValueError(f"LLM response processing failed: {e}")

        if not content:
            raise ValueError("LLM returned empty content")

        return content
# #endregion LLMClient
