"""Message — modelo de mensaje multipart para conversaciones con agentes."""

import dataclasses
import json
import math
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import ClassVar

from context_part import ContextPart
from part import Part, part_from_dict
from text_part import TextPart
from tool_part import ToolPart


@dataclass
class Message:
    """Modelo de mensaje multipart para conversaciones con agentes.

    Soporta texto, referencias a contexto (memoria/recurso/skill) y llamadas
    a herramientas en un mensaje unificado. Disenado para ser serializable a
    JSONL para persistencia.

    Attributes:
        id:         Identificador unico del mensaje.
        role:       Rol del emisor: "user" | "assistant".
        parts:      Lista de partes del mensaje (TextPart, ContextPart, ToolPart).
        created_at: Timestamp de creacion en UTC.
    """

    id: str
    role: str
    parts: list[Part]
    created_at: datetime | None = None

    # --- Properties ---

    @property
    def content(self) -> str:
        """Texto del primer TextPart del mensaje. Vacio si no hay TextPart."""
        for part in self.parts:
            if isinstance(part, TextPart):
                return part.text
        return ""

    @property
    def estimated_tokens(self) -> int:
        """Estimacion de tokens consumidos por el mensaje.

        Cuenta caracteres de TextParts, abstracts de ContextParts, e inputs/outputs
        de ToolParts. Divide entre 4 (heuristica estandar: ~4 chars por token).

        Returns:
            Numero entero de tokens estimados (ceil).
        """
        total_chars = 0
        for part in self.parts:
            if isinstance(part, TextPart):
                total_chars += len(part.text)
            elif isinstance(part, ContextPart):
                total_chars += len(part.abstract)
            elif isinstance(part, ToolPart):
                if part.tool_input:
                    total_chars += len(json.dumps(part.tool_input))
                total_chars += len(part.tool_output)
        return math.ceil(total_chars / 4)

    # --- Serialization ---

    def to_dict(self) -> dict:
        """Serializa el mensaje a un dict compatible con JSONL.

        Returns:
            Dict con todos los campos del mensaje listos para json.dumps().
        """
        return {
            "id": self.id,
            "role": self.role,
            "parts": [dataclasses.asdict(p) for p in self.parts],
            "created_at": self.created_at.isoformat() if self.created_at else None,
        }

    def to_jsonl(self) -> str:
        """Serializa el mensaje a un string JSON en una sola linea (JSONL).

        Returns:
            String JSON sin saltos de linea internos.
        """
        return json.dumps(self.to_dict(), ensure_ascii=False)

    @classmethod
    def from_dict(cls, data: dict) -> "Message":
        """Deserializa un Message desde un dict.

        Args:
            data: Dict con los campos del mensaje. Los dicts de 'parts'
                  se deserializan con part_from_dict().

        Returns:
            Instancia de Message reconstruida.
        """
        parts = [part_from_dict(p) for p in data.get("parts", [])]
        created_at = None
        if data.get("created_at"):
            created_at = datetime.fromisoformat(data["created_at"])
        return cls(
            id=data["id"],
            role=data["role"],
            parts=parts,
            created_at=created_at,
        )

    # --- Factories ---

    @classmethod
    def create_user(cls, content: str) -> "Message":
        """Crea un mensaje de usuario con un unico TextPart.

        Args:
            content: Texto del mensaje.

        Returns:
            Message con role="user" y un TextPart.
        """
        return cls(
            id=str(uuid.uuid4()),
            role="user",
            parts=[TextPart(text=content)],
            created_at=datetime.now(timezone.utc),
        )

    @classmethod
    def create_assistant(
        cls,
        content: str,
        context_refs: list[ContextPart] | None = None,
        tool_calls: list[ToolPart] | None = None,
    ) -> "Message":
        """Crea un mensaje de asistente con texto, contextos opcionales y tool calls opcionales.

        El orden de las partes es: ContextParts, TextPart, ToolParts.

        Args:
            content:      Texto principal de la respuesta del asistente.
            context_refs: Referencias a contextos que el asistente uso para responder.
            tool_calls:   Llamadas a herramientas realizadas por el asistente.

        Returns:
            Message con role="assistant" y las partes compuestas.
        """
        parts: list[Part] = []
        if context_refs:
            parts.extend(context_refs)
        parts.append(TextPart(text=content))
        if tool_calls:
            parts.extend(tool_calls)
        return cls(
            id=str(uuid.uuid4()),
            role="assistant",
            parts=parts,
            created_at=datetime.now(timezone.utc),
        )

    # --- Accessors ---

    def get_context_parts(self) -> list[ContextPart]:
        """Devuelve todas las partes de tipo ContextPart del mensaje.

        Returns:
            Lista (posiblemente vacia) de ContextParts.
        """
        return [p for p in self.parts if isinstance(p, ContextPart)]

    def get_tool_parts(self) -> list[ToolPart]:
        """Devuelve todas las partes de tipo ToolPart del mensaje.

        Returns:
            Lista (posiblemente vacia) de ToolParts.
        """
        return [p for p in self.parts if isinstance(p, ToolPart)]

    def find_tool_part(self, tool_id: str) -> ToolPart | None:
        """Busca un ToolPart por su tool_id.

        Args:
            tool_id: Identificador de la invocacion de la herramienta.

        Returns:
            El ToolPart encontrado, o None si no existe.
        """
        for part in self.parts:
            if isinstance(part, ToolPart) and part.tool_id == tool_id:
                return part
        return None
