"""
Provider Router — Smart model routing with fallback chains.

Defines the LLMProvider ABC and routes requests to the configured
primary provider with automatic fallback on failure.
"""

from __future__ import annotations

import asyncio
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any


# ---------------------------------------------------------------------------
# LLM response models
# ---------------------------------------------------------------------------

@dataclass
class ToolCall:
    """A tool call from the LLM."""
    id: str
    name: str
    arguments: dict[str, Any]

    def to_dict(self) -> dict[str, Any]:
        return {
            "id": self.id,
            "type": "function",
            "function": {
                "name": self.name,
                "arguments": self.arguments,
            },
        }


@dataclass
class LLMResponse:
    """Unified response from any LLM provider."""
    content: str | None = None
    tool_calls: list[ToolCall] | None = None
    model: str = ""
    usage: dict[str, int] = field(default_factory=dict)
    raw: dict[str, Any] = field(default_factory=dict)

    @property
    def has_tool_calls(self) -> bool:
        return bool(self.tool_calls)


# ---------------------------------------------------------------------------
# Abstract base provider
# ---------------------------------------------------------------------------

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

    name: str = "base"

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

    @abstractmethod
    async def is_available(self) -> bool:
        """Check if the provider is configured and reachable."""
        ...


# ---------------------------------------------------------------------------
# Provider Router
# ---------------------------------------------------------------------------

class ProviderRouter:
    """
    Smart model routing with fallback chain and retry logic.

    Tries the primary provider first. On failure, tries each fallback
    in order. Implements exponential backoff on rate limits.
    """

    def __init__(
        self,
        primary: LLMProvider,
        fallbacks: list[LLMProvider] | None = None,
        max_retries: int = 2,
    ) -> None:
        self._primary = primary
        self._fallbacks = fallbacks or []
        self._max_retries = max_retries

    @property
    def name(self) -> str:
        return self._primary.name

    async def complete(
        self,
        messages: list[dict[str, Any]],
        tools: list[dict[str, Any]] | None = None,
        temperature: float = 0.7,
        max_tokens: int = 4096,
    ) -> LLMResponse:
        """Route completion request with fallback logic."""

        # Try primary
        for attempt in range(self._max_retries + 1):
            try:
                return await self._primary.complete(
                    messages=messages,
                    tools=tools,
                    temperature=temperature,
                    max_tokens=max_tokens,
                )
            except Exception as e:
                if attempt < self._max_retries:
                    await asyncio.sleep(2 ** attempt)  # Exponential backoff
                    continue
                last_error = e
                break

        # Try fallbacks
        for provider in self._fallbacks:
            try:
                if await provider.is_available():
                    return await provider.complete(
                        messages=messages,
                        tools=tools,
                        temperature=temperature,
                        max_tokens=max_tokens,
                    )
            except Exception:
                continue

        raise RuntimeError(
            f"All providers failed. Primary: {self._primary.name}. "
            f"Last error: {last_error}"
        )

    async def is_available(self) -> bool:
        """Check if any provider is available."""
        if await self._primary.is_available():
            return True
        return any(
            await p.is_available() for p in self._fallbacks
        )
