"""Anthropic Claude Provider 适配器.

基于 httpx 异步调用 Anthropic Messages API（/v1/messages）。
使用 x-api-key 认证 + anthropic-version header。
"""

import json
from collections.abc import AsyncGenerator
from typing import Any

import httpx
import structlog

from ..errors import AILLMUnavailableError
from .base import LLMProvider, LLMResponse, LLMStreamChunk

logger = structlog.get_logger()

ANTHROPIC_API_VERSION = "2023-06-01"


class AnthropicProvider(LLMProvider):
    """Anthropic Claude Provider.

    调用 /v1/messages 端点，支持流式与非流式。
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = "https://api.anthropic.com",
        timeout: float = 30.0,
        stream_connect_timeout: float = 30.0,
        stream_read_timeout: float = 60.0,
    ) -> None:
        self._api_key = api_key
        self._base_url = base_url.rstrip("/")
        self._timeout = timeout
        self._stream_connect_timeout = stream_connect_timeout
        self._stream_read_timeout = stream_read_timeout

    @property
    def name(self) -> str:
        return "anthropic"

    def is_available(self) -> bool:
        return bool(self._api_key)

    def _build_url(self) -> str:
        return f"{self._base_url}/v1/messages"

    def _build_headers(self) -> dict[str, str]:
        return {
            "x-api-key": self._api_key,
            "anthropic-version": ANTHROPIC_API_VERSION,
            "Content-Type": "application/json",
        }

    def _convert_messages(
        self,
        messages: list[dict[str, str]],
    ) -> tuple[str, list[dict[str, str]]]:
        """将 OpenAI 格式消息转换为 Anthropic 格式.

        Anthropic 要求 system 消息单独传递，user/assistant 在 messages 数组。
        Returns:
            (system_prompt, messages)
        """
        system_parts: list[str] = []
        converted: list[dict[str, str]] = []
        for msg in messages:
            if msg.get("role") == "system":
                system_parts.append(msg.get("content", ""))
            else:
                converted.append(
                    {"role": msg.get("role", "user"), "content": msg.get("content", "")},
                )
        return "\n\n".join(system_parts), converted

    async def chat(
        self,
        messages: list[dict[str, str]],
        model: str,
        temperature: float = 0.7,
        **kwargs: Any,
    ) -> LLMResponse:
        if not self.is_available():
            raise AILLMUnavailableError("anthropic api_key not configured")

        system_prompt, converted = self._convert_messages(messages)
        max_tokens = kwargs.pop("max_tokens", 4096)
        payload: dict[str, Any] = {
            "model": model,
            "messages": converted,
            "temperature": temperature,
            "max_tokens": max_tokens,
        }
        if system_prompt:
            payload["system"] = system_prompt
        payload.update(kwargs)

        try:
            async with httpx.AsyncClient(timeout=self._timeout) as client:
                resp = await client.post(
                    self._build_url(),
                    json=payload,
                    headers=self._build_headers(),
                )
                resp.raise_for_status()
                data = resp.json()
        except httpx.HTTPStatusError as exc:
            logger.error(
                "anthropic_chat_http_error",
                status_code=exc.response.status_code,
                body=exc.response.text[:500],
            )
            raise AILLMUnavailableError(
                f"anthropic http {exc.response.status_code}: {exc.response.text[:200]}",
            ) from exc
        except httpx.HTTPError as exc:
            logger.error("anthropic_chat_http_error", error=str(exc))
            raise AILLMUnavailableError(f"anthropic http error: {exc}") from exc

        content_parts = data.get("content", [])
        content = ""
        if content_parts:
            content = content_parts[0].get("text", "") or ""
        usage_raw = data.get("usage", {}) or {}
        prompt_tokens = int(usage_raw.get("input_tokens", 0))
        completion_tokens = int(usage_raw.get("output_tokens", 0))
        usage = {
            "prompt_tokens": prompt_tokens,
            "completion_tokens": completion_tokens,
            "total_tokens": prompt_tokens + completion_tokens,
        }
        return LLMResponse(
            content=content,
            model=data.get("model", model),
            usage=usage,
            provider=self.name,
            raw=data,
        )

    async def stream_chat(
        self,
        messages: list[dict[str, str]],
        model: str,
        temperature: float = 0.7,
        **kwargs: Any,
    ) -> AsyncGenerator[LLMStreamChunk, None]:
        if not self.is_available():
            raise AILLMUnavailableError("anthropic api_key not configured")

        system_prompt, converted = self._convert_messages(messages)
        max_tokens = kwargs.pop("max_tokens", 4096)
        payload: dict[str, Any] = {
            "model": model,
            "messages": converted,
            "temperature": temperature,
            "max_tokens": max_tokens,
            "stream": True,
        }
        if system_prompt:
            payload["system"] = system_prompt
        payload.update(kwargs)

        timeout = httpx.Timeout(
            connect=self._stream_connect_timeout,
            read=self._stream_read_timeout,
            write=self._stream_connect_timeout,
            pool=self._stream_connect_timeout,
        )

        try:
            async with (
                httpx.AsyncClient(timeout=timeout) as client,
                client.stream(
                    "POST",
                    self._build_url(),
                    json=payload,
                    headers=self._build_headers(),
                ) as resp,
            ):
                resp.raise_for_status()
                async for line in resp.aiter_lines():
                    chunk = self._parse_sse_line(line, model)
                    if chunk is not None:
                        yield chunk
                        if chunk.finish_reason == "end_turn":
                            return
        except httpx.HTTPStatusError as exc:
            logger.error(
                "anthropic_stream_http_error",
                status_code=exc.response.status_code,
            )
            raise AILLMUnavailableError(
                f"anthropic stream http {exc.response.status_code}",
            ) from exc
        except httpx.HTTPError as exc:
            logger.error("anthropic_stream_http_error", error=str(exc))
            raise AILLMUnavailableError(f"anthropic stream error: {exc}") from exc

    @staticmethod
    def _parse_sse_line(line: str, model: str) -> LLMStreamChunk | None:
        """解析 Anthropic SSE 事件流.

        Anthropic 事件格式：
            event: content_block_delta
            data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"..."}}

        关键事件：
            content_block_delta: 文本增量
            message_stop: 结束
        """
        if not line or not line.startswith("data: "):
            return None
        data_str = line[len("data: "):]
        try:
            data = json.loads(data_str)
        except json.JSONDecodeError:
            return None
        event_type = data.get("type", "")
        if event_type == "content_block_delta":
            delta = data.get("delta", {})
            if delta.get("type") == "text_delta":
                return LLMStreamChunk(
                    delta=delta.get("text", ""),
                    model=model,
                    provider="anthropic",
                )
            return None
        if event_type == "message_stop":
            return LLMStreamChunk(
                delta="",
                model=model,
                finish_reason="end_turn",
                provider="anthropic",
            )
        return None
