"""
Message — the fundamental communication unit between agents.

Design notes:
  - Compatible with both A2A Message and OpenAI ChatCompletion message formats
  - Supports multi-part content (text, images, structured data)
  - Immutable once created (append-only message history)
"""

from __future__ import annotations

from datetime import datetime
from enum import StrEnum
from typing import Any

from pydantic import BaseModel, Field

from nexus.types import ContentPart, new_id


class Role(StrEnum):
    """Message sender role, bridging A2A and OpenAI conventions."""

    USER = "user"
    AGENT = "agent"  # A2A style — maps to "assistant" for OpenAI
    SYSTEM = "system"
    TOOL = "tool"

    def to_openai(self) -> str:
        """Convert to OpenAI-compatible role string."""
        if self == Role.AGENT:
            return "assistant"
        return self.value


class Message(BaseModel):
    """
    A single message in a conversation.

    Designed to be:
    - Serializable (for checkpointing and A2A transport)
    - Multi-modal (text + images + structured data via ContentPart)
    - Traceable (every message has an id and timestamp)
    """

    id: str = Field(default_factory=new_id)
    role: Role
    content: str = ""
    parts: list[ContentPart] = Field(default_factory=list)
    sender_id: str | None = None
    receiver_id: str | None = None
    context_id: str | None = None  # A2A contextId — groups related messages
    tool_call_id: str | None = None  # For tool response messages
    tool_calls: list[dict[str, Any]] | None = None  # For requesting tool calls
    metadata: dict[str, Any] = Field(default_factory=dict)
    timestamp: datetime = Field(default_factory=datetime.utcnow)

    @classmethod
    def user(cls, content: str, **kwargs: Any) -> Message:
        """Create a user message."""
        return cls(role=Role.USER, content=content, **kwargs)

    @classmethod
    def agent(cls, content: str, sender_id: str | None = None, **kwargs: Any) -> Message:
        """Create an agent message."""
        return cls(role=Role.AGENT, content=content, sender_id=sender_id, **kwargs)

    @classmethod
    def system(cls, content: str, **kwargs: Any) -> Message:
        """Create a system message."""
        return cls(role=Role.SYSTEM, content=content, **kwargs)

    @classmethod
    def tool_result(
        cls,
        content: str,
        tool_call_id: str,
        **kwargs: Any,
    ) -> Message:
        """Create a tool response message."""
        return cls(
            role=Role.TOOL,
            content=content,
            tool_call_id=tool_call_id,
            **kwargs,
        )

    def to_openai_dict(self) -> dict[str, Any]:
        """Convert to OpenAI ChatCompletion message format."""
        msg: dict[str, Any] = {
            "role": self.role.to_openai(),
            "content": self.content,
        }
        if self.tool_call_id:
            msg["tool_call_id"] = self.tool_call_id
        if self.tool_calls:
            msg["tool_calls"] = self.tool_calls
        return msg


class Conversation(BaseModel):
    """An append-only sequence of messages forming a conversation history."""

    id: str = Field(default_factory=new_id)
    messages: list[Message] = Field(default_factory=list)
    context_id: str = Field(default_factory=new_id)

    def append(self, message: Message) -> None:
        """Add a message to the conversation."""
        if not message.context_id:
            message.context_id = self.context_id
        self.messages.append(message)

    def to_openai_messages(self) -> list[dict[str, Any]]:
        """Export the full conversation in OpenAI format."""
        return [m.to_openai_dict() for m in self.messages]

    @property
    def last(self) -> Message | None:
        return self.messages[-1] if self.messages else None

    def __len__(self) -> int:
        return len(self.messages)
