# #region LLMHttpClient [C:3] [TYPE Module] [SEMANTICS translate, llm, http, openai, retry, rate-limit]
# @BRIEF HTTP client for OpenAI-compatible LLM API calls with rate-limit handling and
# @RATIONALE Extracted 'openai' default provider type and 8192 default max_tokens into module-level constants DEFAULT_PROVIDER_TYPE and DEFAULT_MAX_TOKENS.

#         structured output fallback. Extracted from _llm_call.py for INV_7 compliance.
# @LAYER Infrastructure
# @RELATION DEPENDS_ON -> [EXT:requests]
# @PRE Valid API endpoint, key, model, and prompt.
# @POST Returns (response text, finish_reason) tuple.
# @SIDE_EFFECT HTTP POST to LLM API with optional retry on 429.
# @RATIONALE Extracted from LLMTranslationService (793 lines) to keep module under INV_7 limit.
# @REJECTED Single HTTP client class — kept as module-level functions for stateless reusability.

import os
import 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


# #region call_openai_compatible [C:3] [TYPE Function] [SEMANTICS translate, llm, http, openai]
# @BRIEF Call OpenAI-compatible API with rate-limit handling and structured output fallback.
# @PRE Valid API endpoint, key, model, and prompt.
# @POST Returns (response text, finish_reason).
# @SIDE_EFFECT HTTP POST to LLM API.
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,
) -> tuple[str, str | None]:
    """Call OpenAI-compatible API for batch translation."""
    if not base_url:
        raise ValueError("LLM provider has no base_url configured")


    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} "
        f"response_format={'yes' if 'response_format' in payload else 'no'} "
        f"prompt_len={len(prompt)}"
    )

    response, response_text = _do_http_request(url, headers, payload)
    _handle_response_format_fallback(response, response_text, payload, url, headers)

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

    choices = data.get("choices", [])
    if not choices:
        logger.explore("LLM returned no choices", extra={
            "src": "executor", "response_keys": list(data.keys()),
            "response_preview": str(data)[:2000],
        })
        raise ValueError("LLM returned no choices")

    try:
        finish_reason = choices[0].get("finish_reason") or "none"
        msg = choices[0].get("message") or {}
    except (TypeError, AttributeError) as e:
        logger.explore("TypeError processing LLM response choices", extra={
            "src": "executor_diag", "error": str(e),
            "choices_0_type": type(choices[0]).__name__ if choices else "N/A",
            "choices_0_repr": repr(choices[0])[:2000] if choices else "N/A",
            "data_type": type(data).__name__, "data_preview": str(data)[:2000],
        })
        raise ValueError(f"LLM response processing failed: {e}")

    refusal = msg.get("refusal") if isinstance(msg, dict) else None
    if refusal:
        logger.explore("LLM refused to respond", extra={
            "src": "executor", "refusal": str(refusal)[:500], "finish_reason": finish_reason,
        })
        raise ValueError(f"LLM refused to respond: {refusal}")

    content = msg.get("content") if isinstance(msg, dict) else ""
    if not content and isinstance(msg, dict):
        content = msg.get("content") or ""

    logger.reason(
        f"LLM response finish_reason={finish_reason} content_len={len(content)} "
        f"msg_keys={list(msg.keys()) if isinstance(msg, dict) else []}"
    )
    if not content:
        logger.explore("LLM returned empty content", extra={
            "src": "executor", "finish_reason": finish_reason,
            "msg_keys": list(msg.keys()) if isinstance(msg, dict) else [],
            "response_preview": str(data)[:2000],
        })
        raise ValueError("LLM returned empty content")

    return content, finish_reason
# #endregion call_openai_compatible


# #region _do_http_request [C:1] [TYPE Function]
def _do_http_request(url: str, headers: dict, payload: dict) -> tuple[Any, str]:
    """Make HTTP POST with rate-limit (429) retry handling."""
    import requests as http_requests
    _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=180, verify=_get_verify())
        response_text = response.text
        if response.status_code == 429:
            _retry_count_429 += 1
            retry_after = response.headers.get("Retry-After")
            if retry_after:
                try:
                    wait = int(retry_after)
                except (ValueError, TypeError):
                    wait = 2 ** _retry_count_429
            else:
                wait = 2 ** _retry_count_429
            logger.explore(f"Rate limited (429), retry {_retry_count_429}/{_max_retry_429} after {wait}s",
                           extra={"src": "executor", "retry_after": retry_after, "wait": wait})
            time.sleep(wait)
            if _retry_count_429 >= _max_retry_429:
                break
        else:
            break
    return response, response_text
# #endregion _do_http_request


# #region _handle_response_format_fallback [C:1] [TYPE Function]
def _handle_response_format_fallback(
    response: Any, response_text: str, payload: dict, url: str, headers: dict,
) -> None:
    """Handle 400 errors from structured_outputs not being supported."""
    import requests as http_requests
    _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 _patterns)
    ):
        logger.explore("Structured outputs not supported, retrying without response_format",
                       extra={"src": "executor"})
        payload.pop("response_format", None)
        new_response = http_requests.post(url, headers=headers, json=payload, timeout=180, verify=_get_verify())
        response.status_code = new_response.status_code
        response._content = new_response.content
        response.encoding = new_response.encoding
        response.headers = new_response.headers
# #endregion _handle_response_format_fallback
# #endregion LLMHttpClient
