"""LLM 客户端 - 使用 httpx 直接调用 OpenAI 兼容 REST API。

设计要点：
- 不依赖 openai SDK，纯 httpx 异步调用
- api_key 为空或调用失败时返回 None / yield 降级骨架数据
- 调用方据此决定是否进入降级路径
"""

from collections.abc import AsyncGenerator
from typing import Any

import httpx
import structlog

logger = structlog.get_logger()

# 非流式请求默认超时（秒）
DEFAULT_TIMEOUT: float = 30.0
# 流式请求建立连接超时（秒）；读取通过迭代器控制
STREAM_CONNECT_TIMEOUT: float = 30.0
# 流式读取单次 chunk 超时（秒）
STREAM_READ_TIMEOUT: float = 60.0


def _build_url(base_url: str) -> str:
    """拼接 chat completions 端点 URL."""
    return f"{base_url.rstrip('/')}/chat/completions"


def _build_headers(api_key: str) -> dict[str, str]:
    """构建请求头."""
    return {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    }


async def chat_completion(
    messages: list[dict[str, Any]],
    model: str,
    temperature: float,
    api_key: str,
    base_url: str,
) -> dict[str, Any] | None:
    """非流式调用 LLM。

    Returns:
        OpenAI 兼容的响应 dict；api_key 为空或调用失败时返回 None（由调用方降级）。
    """
    if not api_key:
        logger.warning("llm_chat_completion_no_api_key_degraded")
        return None

    url = _build_url(base_url)
    headers = _build_headers(api_key)
    payload = {
        "model": model,
        "messages": messages,
        "temperature": temperature,
        "stream": False,
    }

    try:
        async with httpx.AsyncClient(timeout=DEFAULT_TIMEOUT) as client:
            resp = await client.post(url, json=payload, headers=headers)
            resp.raise_for_status()
            return resp.json()
    except httpx.HTTPStatusError as exc:
        logger.error(
            "llm_chat_completion_http_error",
            status_code=exc.response.status_code,
            body=exc.response.text[:500],
        )
        return None
    except Exception as exc:  # noqa: BLE001 - 顶层兜底，所有异常均降级
        logger.error("llm_chat_completion_failed", error=str(exc))
        return None


async def chat_completion_stream(
    messages: list[dict[str, Any]],
    model: str,
    temperature: float,
    api_key: str,
    base_url: str,
) -> AsyncGenerator[str, None]:
    """流式调用 LLM，以 SSE 格式（``data: <chunk>\\n\\n``）yield。

    api_key 为空或调用失败时 yield 降级骨架数据，保证下游始终能消费。
    """
    if not api_key:
        logger.warning("llm_stream_no_api_key_degraded")
        yield (
            'data: {"choices":[{"delta":{"content":"[degraded] LLM API key not configured"}}]}\n\n'
        )
        yield "data: [DONE]\n\n"
        return

    url = _build_url(base_url)
    headers = _build_headers(api_key)
    payload = {
        "model": model,
        "messages": messages,
        "temperature": temperature,
        "stream": True,
    }

    timeout = httpx.Timeout(
        connect=STREAM_CONNECT_TIMEOUT,
        read=STREAM_READ_TIMEOUT,
        write=STREAM_CONNECT_TIMEOUT,
        pool=STREAM_CONNECT_TIMEOUT,
    )

    try:
        async with (
            httpx.AsyncClient(timeout=timeout) as client,
            client.stream("POST", url, json=payload, headers=headers) as resp,
        ):
            resp.raise_for_status()
            async for line in resp.aiter_lines():
                if not line or not line.startswith("data: "):
                    continue
                yield f"{line}\n\n"
                if line.strip() == "data: [DONE]":
                    return
    except httpx.HTTPStatusError as exc:
        logger.error(
            "llm_stream_http_error_degraded",
            status_code=exc.response.status_code,
        )
        yield 'data: {"choices":[{"delta":{"content":"[degraded] LLM stream HTTP error"}}]}\n\n'
        yield "data: [DONE]\n\n"
    except Exception as exc:  # noqa: BLE001 - 顶层兜底，所有异常均降级
        logger.error("llm_stream_failed_degraded", error=str(exc))
        yield 'data: {"choices":[{"delta":{"content":"[degraded] LLM stream error"}}]}\n\n'
        yield "data: [DONE]\n\n"
