"""Contrato del modelo de lenguaje (independiente del proveedor)."""

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field


@dataclass
class ChatMessage:
    role: str  # system | user | assistant
    content: str


@dataclass
class LlmReply:
    text: str
    raw: dict | None = None
    prompt_tokens: int = 0
    completion_tokens: int = 0


class LlmFatalError(RuntimeError):
    """Error de LLM que no debe reintentarse (cuota, auth, etc.)."""

    def __init__(self, message: str, *, code: str = "") -> None:
        super().__init__(message)
        self.code = code


class LlmQuotaExceeded(LlmFatalError):
    """Cuota / rate limit de OpenAI agotada."""

    def __init__(self, message: str, *, code: str = "rate_limit_exceeded") -> None:
        super().__init__(message, code=code)


@dataclass
class ConversationContext:
    """Contexto de la llamada para el prompt."""

    call_id: str
    document_id: str | None = None
    caller_number: str | None = None
    # Datos CRM de /polizas/{document_id}
    poliza: dict | None = None
    extra: dict = field(default_factory=dict)


class LanguageModel(ABC):
    """Genera la respuesta conversacional del bot.

    Implementación actual: ``llm.openai_llm.OpenAiLanguageModel``.
    """

    provider_name: str

    @abstractmethod
    async def reply(
        self,
        messages: list[ChatMessage],
        *,
        context: ConversationContext | None = None,
    ) -> LlmReply:
        ...

    async def extract_call_result(
        self,
        messages: list[ChatMessage],
        *,
        context: ConversationContext | None = None,
        transcript: list[str] | None = None,
    ) -> dict | None:
        """Extrae resultado estructurado al fin de la llamada (json_schema).

        Default: no soportado. OpenAI Responses lo implementa con
        ``text.format.type=json_schema``.
        """
        return None

    async def warmup(self) -> None:
        """Precarga el modelo (p. ej. Ollama). No-op por defecto."""
        return None

    async def close(self) -> None:
        return None
