"""Abstract base class for LLM / Agent providers.

All LLM implementations must subclass BaseAgent and implement
the conversation interface for voice AI reasoning.
"""

from __future__ import annotations

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

from pydantic import BaseModel, Field

from voxtra.config import LLMConfig


class AgentResponse(BaseModel):
    """Response from an LLM agent."""

    text: str = ""
    tool_calls: list[dict[str, Any]] = Field(default_factory=list)
    finish_reason: str = ""
    usage: dict[str, int] = Field(default_factory=dict)


class BaseAgent(ABC):
    """Abstract interface for LLM / Agent providers.

    The agent handles the reasoning layer of the voice pipeline.
    It receives transcribed user speech and generates text responses
    that will be synthesized back to the caller.

    Implementations should support:
    - Conversation history management
    - System prompts
    - Streaming responses for low latency
    - Tool/function calling (optional)

    Example implementation::

        class MyAgent(BaseAgent):
            async def connect(self):
                self._client = create_llm_client(self.config.api_key)

            async def respond(self, text, history=None):
                response = await self._client.chat(text)
                return AgentResponse(text=response.content)

            async def respond_stream(self, text, history=None):
                async for chunk in self._client.stream(text):
                    yield chunk.content

            async def disconnect(self):
                pass
    """

    def __init__(self, config: LLMConfig) -> None:
        self.config = config
        self._conversation_history: list[dict[str, str]] = []

    @abstractmethod
    async def connect(self) -> None:
        """Initialize the LLM client."""
        ...

    @abstractmethod
    async def respond(
        self,
        text: str,
        *,
        history: list[dict[str, str]] | None = None,
        system_prompt: str | None = None,
    ) -> AgentResponse:
        """Generate a complete response to user input.

        Args:
            text: The user's transcribed speech.
            history: Optional conversation history override.
            system_prompt: Optional system prompt override.

        Returns:
            AgentResponse with the generated text.
        """
        ...

    @abstractmethod
    async def respond_stream(
        self,
        text: str,
        *,
        history: list[dict[str, str]] | None = None,
        system_prompt: str | None = None,
    ) -> AsyncIterator[str]:
        """Stream response tokens for low-latency TTS.

        Yields partial text chunks as they are generated by the LLM.
        This enables the TTS to start synthesizing before the full
        response is complete, reducing end-to-end latency.

        Args:
            text: The user's transcribed speech.
            history: Optional conversation history override.
            system_prompt: Optional system prompt override.

        Yields:
            Text chunks (partial sentences or words).
        """
        ...
        yield  # type: ignore[misc]

    @abstractmethod
    async def disconnect(self) -> None:
        """Close the LLM client and release resources."""
        ...

    def reset_history(self) -> None:
        """Clear the conversation history."""
        self._conversation_history.clear()

    def add_to_history(self, role: str, content: str) -> None:
        """Add a message to the conversation history."""
        self._conversation_history.append({"role": role, "content": content})

    @property
    def history(self) -> list[dict[str, str]]:
        """Return the current conversation history."""
        return list(self._conversation_history)

    async def __aenter__(self) -> BaseAgent:
        await self.connect()
        return self

    async def __aexit__(self, *args: Any) -> None:
        await self.disconnect()
