"""
ModelProvider — abstract interface for LLM backends.

Design notes:
  - All providers implement a single `chat()` method
  - The interface returns a standardized response dict with:
    - "message": a Message object
    - "usage": a TokenUsage object
  - Tool calling is normalized across providers
  - Providers handle their own authentication and retry logic
  - Streaming is supported via `chat_stream()` which yields string chunks
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import AsyncIterator
from typing import Any


class ModelProvider(ABC):
    """
    Abstract base class for LLM providers.

    All providers normalize their responses to a common format so that
    Agent code is completely model-agnostic.

    Implementations:
    - OpenAIProvider: OpenAI GPT models (and compatible APIs)
    - AnthropicProvider: Claude models
    - OllamaProvider: Local models via Ollama
    - LiteLLMProvider: 100+ providers via LiteLLM
    """

    @abstractmethod
    async def chat(
        self,
        messages: list[dict[str, Any]],
        tools: list[dict[str, Any]] | None = None,
        temperature: float = 0.7,
        max_tokens: int | None = None,
        **kwargs: Any,
    ) -> dict[str, Any]:
        """
        Send a chat completion request to the model.

        Args:
            messages: List of messages in OpenAI format
                      [{"role": "user", "content": "..."}]
            tools: Optional list of tool definitions in OpenAI function calling format
            temperature: Sampling temperature (0.0 = deterministic, 1.0 = creative)
            max_tokens: Maximum tokens to generate
            **kwargs: Provider-specific parameters

        Returns:
            A dict containing:
            - "message": Message object with the assistant's response
            - "usage": TokenUsage dict with token counts
            - "model": str with the model name used
            - "raw": the raw provider response (for debugging)
        """
        ...

    async def chat_stream(
        self,
        messages: list[dict[str, Any]],
        tools: list[dict[str, Any]] | None = None,
        temperature: float = 0.7,
        max_tokens: int | None = None,
        **kwargs: Any,
    ) -> AsyncIterator[str]:
        """
        Stream a chat completion, yielding text chunks as they arrive.

        Default implementation falls back to non-streaming chat() and
        yields the full response as a single chunk.  Providers should
        override this for true token-by-token streaming.

        Yields:
            str: Text content chunks
        """
        response = await self.chat(
            messages=messages, tools=tools,
            temperature=temperature, max_tokens=max_tokens, **kwargs,
        )
        content = response["message"].content
        if content:
            yield content

    @abstractmethod
    async def close(self) -> None:
        """Clean up resources (HTTP clients, etc.)."""
        ...

    @property
    @abstractmethod
    def model_name(self) -> str:
        """The name/ID of the model being used."""
        ...

    def __repr__(self) -> str:
        return f"{type(self).__name__}(model={self.model_name!r})"
