"""
Tests for Agentbyte message types.

Run with: uv run pytest tests/test_messages.py -v
"""
import pytest
from pydantic import ValidationError

from agentbyte.messages import (
    AssistantMessage,
    MultiModalMessage,
    SystemMessage,
    ToolCallRequest,
    ToolMessage,
    Usage,
    UserMessage,
)


# ============================================================================
# Tests: Message Types
# ============================================================================


class TestSystemMessage:
    """Test SystemMessage creation and validation."""

    def test_system_message_creation(self):
        """Test creating a system message."""
        msg = SystemMessage(content="You are helpful.", source="system")
        assert msg.role == "system"
        assert msg.content == "You are helpful."

    def test_system_message_role_default(self):
        """Test system message role is automatically set."""
        msg = SystemMessage(content="test", source="system")
        assert msg.role == "system"

    def test_system_message_requires_content(self):
        """Test system message requires content."""
        with pytest.raises(ValidationError):
            SystemMessage(source="system")


class TestUserMessage:
    """Test UserMessage creation and validation."""

    def test_user_message_creation(self):
        """Test creating a user message."""
        msg = UserMessage(content="What is AI?", source="user")
        assert msg.role == "user"
        assert msg.content == "What is AI?"

    def test_user_message_role_default(self):
        """Test user message role is automatically set."""
        msg = UserMessage(content="test", source="user")
        assert msg.role == "user"

    def test_user_message_requires_content(self):
        """Test user message requires content."""
        with pytest.raises(ValidationError):
            UserMessage(source="user")

    def test_multiple_user_messages(self):
        """Test creating multiple user messages."""
        msg1 = UserMessage(content="First question?", source="user")
        msg2 = UserMessage(content="Second question?", source="user")
        assert msg1.content != msg2.content


class TestAssistantMessage:
    """Test AssistantMessage with and without tool calls."""

    def test_assistant_message_text_only(self):
        """Test creating an assistant message with text only."""
        msg = AssistantMessage(content="The answer is 42.", source="gpt-4")
        assert msg.role == "assistant"
        assert msg.content == "The answer is 42."
        assert msg.tool_calls is None

    def test_assistant_message_role_default(self):
        """Test assistant message role is automatically set."""
        msg = AssistantMessage(content="test", source="gpt-4")
        assert msg.role == "assistant"

    def test_assistant_message_with_tool_calls(self):
        """Test assistant message with tool calls."""
        tool_call = ToolCallRequest(
            call_id="call_123",
            tool_name="get_weather",
            parameters={"location": "Paris"},
        )
        msg = AssistantMessage(
            content="I'll check the weather.",
            source="gpt-4",
            tool_calls=[tool_call],
        )
        assert msg.content == "I'll check the weather."
        assert len(msg.tool_calls) == 1
        assert msg.tool_calls[0].tool_name == "get_weather"

    def test_assistant_message_multiple_tool_calls(self):
        """Test assistant message with multiple tool calls."""
        tool_calls = [
            ToolCallRequest(call_id="call_1", tool_name="search", parameters={"q": "a"}),
            ToolCallRequest(call_id="call_2", tool_name="analyze", parameters={"data": "b"}),
        ]
        msg = AssistantMessage(
            content="Processing multiple tasks.",
            source="gpt-4",
            tool_calls=tool_calls,
        )
        assert len(msg.tool_calls) == 2

    def test_assistant_message_requires_content(self):
        """Test assistant message requires content."""
        with pytest.raises(ValidationError):
            AssistantMessage(source="gpt-4")


class TestToolMessage:
    """Test ToolMessage creation and validation."""

    def test_tool_message_creation(self):
        """Test creating a tool message."""
        msg = ToolMessage(
            content="Result: 42",
            source="system",
            tool_call_id="call_123",
            tool_name="calculator",
            success=True,
        )
        assert msg.role == "tool"
        assert msg.content == "Result: 42"
        assert msg.tool_call_id == "call_123"

    def test_tool_message_role_default(self):
        """Test tool message role is automatically set."""
        msg = ToolMessage(
            content="result",
            source="system",
            tool_call_id="id",
            tool_name="func",
            success=True,
        )
        assert msg.role == "tool"

    def test_tool_message_requires_content(self):
        """Test tool message requires content."""
        with pytest.raises(ValidationError):
            ToolMessage(source="system", tool_call_id="id", tool_name="func", success=True)

    def test_tool_message_requires_tool_call_id(self):
        """Test tool message requires tool_call_id."""
        with pytest.raises(ValidationError):
            ToolMessage(content="result", source="system", tool_name="func", success=True)


class TestMultiModalMessage:
    """Test MultiModalMessage creation and validation."""

    def test_multimodal_message_with_media_url(self):
        """Test MultiModalMessage accepts media_url."""
        msg = MultiModalMessage(
            content="See this",
            source="user",
            role="user",
            mime_type="image/png",
            media_url="https://example.com/image.png",
        )
        assert msg.is_image() is True

    def test_multimodal_message_requires_data_or_url(self):
        """Test MultiModalMessage requires data or media_url."""
        with pytest.raises(ValidationError):
            MultiModalMessage(
                content="missing",
                source="user",
                role="user",
                mime_type="image/png",
            )


# ============================================================================
# Tests: ToolCall
# ============================================================================


class TestToolCall:
    """Test ToolCallRequest creation and validation."""

    def test_tool_call_creation(self):
        """Test creating a tool call."""
        tool_call = ToolCallRequest(
            call_id="call_1",
            tool_name="get_weather",
            parameters={"location": "Paris", "unit": "celsius"},
        )
        assert tool_call.call_id == "call_1"
        assert tool_call.tool_name == "get_weather"
        assert tool_call.parameters["location"] == "Paris"

    def test_tool_call_requires_id(self):
        """Test tool call requires ID."""
        with pytest.raises(ValidationError):
            ToolCallRequest(tool_name="func", parameters={})

    def test_tool_call_requires_name(self):
        """Test tool call requires function name."""
        with pytest.raises(ValidationError):
            ToolCallRequest(call_id="1", parameters={})

    def test_tool_call_requires_arguments(self):
        """Test tool call requires arguments dict."""
        with pytest.raises(ValidationError):
            ToolCallRequest(call_id="1", tool_name="func")

    def test_tool_call_with_empty_arguments(self):
        """Test tool call can have empty arguments."""
        tool_call = ToolCallRequest(
            call_id="call_1",
            tool_name="no_args_func",
            parameters={},
        )
        assert tool_call.parameters == {}


# ============================================================================
# Tests: Usage Tracking
# ============================================================================


class TestUsage:
    """Test token usage tracking."""

    def test_usage_creation(self):
        """Test creating usage object."""
        usage = Usage(tokens_input=150, tokens_output=50)
        assert usage.tokens_input == 150
        assert usage.tokens_output == 50

    def test_usage_total_tokens(self):
        """Test total tokens calculation."""
        usage = Usage(tokens_input=150, tokens_output=50)
        assert usage.total_tokens == 200

    def test_usage_requires_non_negative(self):
        """Test usage requires non-negative token counts."""
        Usage(tokens_input=0, tokens_output=0)

        with pytest.raises(ValidationError):
            Usage(tokens_input=-1, tokens_output=0)

    def test_usage_zero_tokens(self):
        """Test usage with zero tokens."""
        usage = Usage(tokens_input=0, tokens_output=0)
        assert usage.total_tokens == 0
        assert usage.duration_ms == 0
        assert usage.llm_calls == 0


# ============================================================================
# Tests: Conversation History
# ============================================================================


class TestConversationHistory:
    """Test building conversation histories."""

    def test_multi_turn_conversation(self):
        """Test building a multi-turn conversation."""
        messages = [
            SystemMessage(content="You are helpful.", source="system"),
            UserMessage(content="Question 1?", source="user"),
            AssistantMessage(content="Answer 1.", source="gpt-4"),
            UserMessage(content="Question 2?", source="user"),
            AssistantMessage(content="Answer 2.", source="gpt-4"),
        ]
        assert len(messages) == 5
        assert messages[0].role == "system"
        assert messages[1].role == "user"

    def test_conversation_with_tools(self):
        """Test conversation with tool calls and results."""
        messages = [
            UserMessage(content="What's the weather?", source="user"),
            AssistantMessage(
                content="I'll check.",
                source="gpt-4",
                tool_calls=[
                    ToolCallRequest(call_id="1", tool_name="weather", parameters={"city": "NYC"})
                ],
            ),
            ToolMessage(
                content="Sunny, 72F",
                source="system",
                tool_call_id="1",
                tool_name="weather",
                success=True,
            ),
            AssistantMessage(content="It's sunny and 72F.", source="gpt-4"),
        ]
        assert len(messages) == 4
        assert isinstance(messages[1], AssistantMessage)
        assert len(messages[1].tool_calls) == 1
        assert isinstance(messages[2], ToolMessage)
        assert messages[2].tool_call_id == "1"


# ============================================================================
# Tests: Message Validation
# ============================================================================


class TestMessageValidation:
    """Test message validation and constraints."""

    def test_all_message_types_have_role(self):
        """Test all message types have role."""
        system = SystemMessage(content="test", source="system")
        user = UserMessage(content="test", source="user")
        assistant = AssistantMessage(content="test", source="gpt-4")
        tool = ToolMessage(content="test", source="system", tool_call_id="1", tool_name="func", success=True)

        assert system.role == "system"
        assert user.role == "user"
        assert assistant.role == "assistant"
        assert tool.role == "tool"

    def test_message_content_cannot_be_empty_string_valid(self):
        """Test that empty content is technically valid (depends on use case)."""
        msg = UserMessage(content="", source="user")
        assert msg.content == ""

    def test_message_content_must_be_string(self):
        """Test content must be string."""
        with pytest.raises(ValidationError):
            UserMessage(content=123)
