from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, Field


class Message(BaseModel):
    """Provider-agnostic chat message."""

    role: Literal["system", "user", "assistant", "tool"]
    content: str | list[dict[str, Any]]
    name: str | None = None
    tool_call_id: str | None = None


class ToolDefinition(BaseModel):
    """Canonical tool definition modeled as JSON Schema parameters."""

    name: str
    description: str
    parameters: dict[str, Any]


class LLMRequest(BaseModel):
    """Provider-neutral completion request."""

    messages: list[Message]
    tools: list[ToolDefinition] = Field(default_factory=list)
    max_tokens: int = 4096
    temperature: float = 0.7
    system: str | None = None
    stop_sequences: list[str] = Field(default_factory=list)
    budget_tokens: int | None = None


class ToolCall(BaseModel):
    """Tool call emitted by a provider response."""

    tool_call_id: str
    tool_name: str
    arguments: dict[str, Any]


class LLMResponse(BaseModel):
    """Provider-neutral completion response."""

    content: str
    tool_calls: list[ToolCall] = Field(default_factory=list)
    input_tokens: int
    output_tokens: int
    model: str
    finish_reason: str
    raw: dict[str, Any] = Field(default_factory=dict)


class AbstractProvider(ABC):
    """Abstract interface implemented by all LLM providers."""

    model_config = ConfigDict(arbitrary_types_allowed=True)

    @abstractmethod
    async def complete(self, request: LLMRequest) -> LLMResponse:
        """Execute a completion request."""

    @abstractmethod
    def count_tokens(self, text: str) -> int:
        """Estimate token count for the supplied text."""

    @property
    @abstractmethod
    def model_name(self) -> str:
        """Return configured model name."""

    @property
    @abstractmethod
    def provider_name(self) -> str:
        """Return provider identifier string."""

    @property
    @abstractmethod
    def context_window(self) -> int:
        """Return context window in tokens."""
