"""LLM Provider 抽象接口（02-architecture-design.md §13.1）.

所有 Provider 适配器实现此接口。ProviderFailoverChain 依赖此抽象进行故障切换。
"""

from abc import ABC, abstractmethod
from collections.abc import AsyncGenerator
from dataclasses import dataclass, field
from typing import Any


@dataclass
class LLMResponse:
    """LLM 非流式响应统一结构.

    各 Provider 适配器将原始响应转换为此结构，屏蔽厂商差异。
    """

    content: str
    model: str
    usage: dict[str, int] = field(
        default_factory=lambda: {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0},
    )
    provider: str = ""
    raw: dict[str, Any] | None = None


@dataclass
class LLMStreamChunk:
    """LLM 流式 chunk 统一结构."""

    delta: str
    model: str
    finish_reason: str | None = None
    provider: str = ""


class LLMProvider(ABC):
    """LLM Provider 抽象基类.

    实现类需提供：
      - name: Provider 标识（用于熔断器 + 日志 + 用量事件）
      - chat: 非流式对话
      - stream_chat: 流式对话（异步生成器）
      - embed: 文本向量化（可选，默认抛 NotImplementedError）
      - is_available: 是否已配置可用
    """

    @property
    @abstractmethod
    def name(self) -> str:
        """Provider 唯一标识（如 'openai' / 'anthropic'）."""

    @abstractmethod
    def is_available(self) -> bool:
        """Provider 是否已配置（API key / base_url 非空）."""

    @abstractmethod
    async def chat(
        self,
        messages: list[dict[str, str]],
        model: str,
        temperature: float = 0.7,
        **kwargs: Any,
    ) -> LLMResponse:
        """非流式对话.

        Args:
            messages: OpenAI 格式消息列表 [{"role": "...", "content": "..."}]
            model: 模型标识
            temperature: 采样温度 0.0-2.0
            **kwargs: Provider 特定参数

        Returns:
            LLMResponse 统一响应

        Raises:
            AILLMUnavailableError: 调用失败（由 FailoverChain 捕获切换）
        """

    @abstractmethod
    async def stream_chat(
        self,
        messages: list[dict[str, str]],
        model: str,
        temperature: float = 0.7,
        **kwargs: Any,
    ) -> AsyncGenerator[LLMStreamChunk, None]:
        """流式对话.

        Yields:
            LLMStreamChunk 统一 chunk
        """

    async def embed(self, text: str, model: str) -> list[float]:
        """文本向量化（可选能力）.

        默认抛 NotImplementedError，支持 embedding 的 Provider 覆写此方法。
        """
        raise NotImplementedError(f"{self.name} does not support embed")
