"""Tests for Message class."""

import pytest
from unittest.mock import Mock
from basion_agent.message import Message
from basion_agent.conversation import Conversation


class TestMessageInitialization:
    """Test Message initialization."""

    def test_message_initialization(self):
        """Test direct message instantiation."""
        message = Message(
            content="Test content",
            conversation_id="conv-123",
            user_id="user-123",
            headers={"conversationId": "conv-123", "from_": "user"},
            raw_body={"content": "Test content"}
        )

        assert message.content == "Test content"
        assert message.conversation_id == "conv-123"
        assert message.headers == {"conversationId": "conv-123", "from_": "user"}
        assert message.raw_body == {"content": "Test content"}
        assert message.conversation is None

    def test_from_kafka_message(self, mock_conversation_client):
        """Test creating message from Kafka message."""
        body = {"content": "Hello world"}
        headers = {
            "conversationId": "conv-456",
            "from_": "user",
            "custom_header": "value"
        }

        message = Message.from_kafka_message(
            body, headers, conversation_client=mock_conversation_client
        )

        assert message.content == "Hello world"
        assert message.conversation_id == "conv-456"
        assert message.headers == headers
        assert message.raw_body == body

    def test_from_kafka_message_extracts_content(self):
        """Test content extraction from message body."""
        body = {"content": "Test message", "extra": "data"}
        headers = {"conversationId": "conv-123"}

        message = Message.from_kafka_message(body, headers)

        assert message.content == "Test message"

    def test_from_kafka_message_extracts_conversation_id(self):
        """Test conversation ID extraction from headers."""
        body = {"content": "Test"}
        headers = {"conversationId": "conv-789", "other": "header"}

        message = Message.from_kafka_message(body, headers)

        assert message.conversation_id == "conv-789"

    def test_from_kafka_message_stores_headers(self):
        """Test all headers are preserved."""
        body = {"content": "Test"}
        headers = {
            "conversationId": "conv-123",
            "from_": "user",
            "custom1": "value1",
            "custom2": "value2"
        }

        message = Message.from_kafka_message(body, headers)

        assert message.headers == headers
        assert "custom1" in message.headers
        assert "custom2" in message.headers

    def test_from_kafka_message_stores_raw_body(self):
        """Test raw body is preserved."""
        body = {
            "content": "Test",
            "metadata": {"key": "value"},
            "extra_field": 123
        }
        headers = {"conversationId": "conv-123"}

        message = Message.from_kafka_message(body, headers)

        assert message.raw_body == body
        assert "metadata" in message.raw_body
        assert "extra_field" in message.raw_body

    def test_conversation_creation(self, mock_conversation_client):
        """Test conversation is created when conversation_id is present."""
        body = {"content": "Test"}
        headers = {"conversationId": "conv-123"}

        message = Message.from_kafka_message(
            body, headers, conversation_client=mock_conversation_client
        )

        # Conversation should be created immediately
        assert message.conversation is not None
        assert isinstance(message.conversation, Conversation)
        assert message.conversation.id == "conv-123"

    def test_conversation_without_client(self):
        """Test conversation is None when no client provided."""
        body = {"content": "Test"}
        headers = {"conversationId": "conv-123"}

        message = Message.from_kafka_message(body, headers, conversation_client=None)

        # Conversation should be None
        assert message.conversation is None


class TestMultipleAttachments:
    """Test multiple attachments handling."""

    attachment1 = {
        "id": "att-1",
        "url": "http://example.com/file1.jpg",
        "filename": "file1.jpg",
        "content_type": "image/jpeg",
        "size": 1024,
    }

    attachment2 = {
        "id": "att-2",
        "url": "http://example.com/file2.pdf",
        "filename": "file2.pdf",
        "content_type": "application/pdf",
        "size": 2048,
    }

    def test_parse_attachments_from_plural_header(self):
        """Test parsing attachments from plural 'attachments' header."""
        import json

        body = {"content": "Test"}
        headers = {
            "conversationId": "conv-123",
            "attachments": json.dumps([self.attachment1, self.attachment2]),
        }

        message = Message.from_kafka_message(body, headers)

        assert message.has_attachment() is True
        assert message.has_attachments() is True
        assert message.get_attachment_count() == 2
        assert len(message.attachments) == 2
        assert message.attachments[0].filename == "file1.jpg"
        assert message.attachments[1].filename == "file2.pdf"

    def test_singular_attachment_is_first_from_plural(self):
        """Test singular 'attachment' is first item for backward compat."""
        import json

        body = {"content": "Test"}
        headers = {
            "conversationId": "conv-123",
            "attachments": json.dumps([self.attachment1, self.attachment2]),
        }

        message = Message.from_kafka_message(body, headers)

        # Singular 'attachment' should be first item
        assert message.attachment is not None
        assert message.attachment.filename == "file1.jpg"

    def test_fallback_to_singular_header(self):
        """Test fallback to singular 'attachment' header when no plural."""
        import json

        body = {"content": "Test"}
        headers = {
            "conversationId": "conv-123",
            "attachment": json.dumps(self.attachment1),
        }

        message = Message.from_kafka_message(body, headers)

        assert message.has_attachments() is True
        assert message.get_attachment_count() == 1
        assert message.attachments[0].filename == "file1.jpg"
        assert message.attachment.filename == "file1.jpg"

    def test_prefer_plural_over_singular(self):
        """Test plural header takes precedence over singular."""
        import json

        body = {"content": "Test"}
        headers = {
            "conversationId": "conv-123",
            "attachment": json.dumps(self.attachment1),
            "attachments": json.dumps([self.attachment2]),
        }

        message = Message.from_kafka_message(body, headers)

        # Should use plural (attachment2), not singular (attachment1)
        assert message.get_attachment_count() == 1
        assert message.attachments[0].filename == "file2.pdf"

    def test_parse_attachments_from_body(self):
        """Test parsing attachments from body when no headers."""
        body = {
            "content": "Test",
            "attachments": [self.attachment1, self.attachment2],
        }
        headers = {"conversationId": "conv-123"}

        message = Message.from_kafka_message(body, headers)

        assert message.get_attachment_count() == 2
        assert message.attachments[0].filename == "file1.jpg"

    def test_empty_attachments_when_none(self):
        """Test empty list when no attachments."""
        body = {"content": "Test"}
        headers = {"conversationId": "conv-123"}

        message = Message.from_kafka_message(body, headers)

        assert message.has_attachments() is False
        assert message.get_attachment_count() == 0
        assert message.attachments == []
        assert message.get_attachments() == []

    def test_get_attachments_returns_list(self):
        """Test get_attachments returns a list."""
        import json

        body = {"content": "Test"}
        headers = {
            "conversationId": "conv-123",
            "attachments": json.dumps([self.attachment1, self.attachment2]),
        }

        message = Message.from_kafka_message(body, headers)

        attachments = message.get_attachments()
        assert isinstance(attachments, list)
        assert len(attachments) == 2

    def test_post_init_syncs_attachment_and_attachments(self):
        """Test __post_init__ syncs singular and plural fields."""
        from basion_agent.attachment_client import AttachmentInfo

        # Create message with only singular attachment
        att_info = AttachmentInfo.from_dict(self.attachment1)
        message = Message(
            content="Test",
            conversation_id="conv-123",
            user_id="user-1",
            headers={},
            raw_body={},
            attachment=att_info,
        )

        # Should have synced to attachments
        assert len(message.attachments) == 1
        assert message.attachments[0].filename == "file1.jpg"

    def test_post_init_sets_attachment_from_attachments(self):
        """Test __post_init__ sets singular from plural."""
        from basion_agent.attachment_client import AttachmentInfo

        # Create message with only plural attachments
        att_infos = [
            AttachmentInfo.from_dict(self.attachment1),
            AttachmentInfo.from_dict(self.attachment2),
        ]
        message = Message(
            content="Test",
            conversation_id="conv-123",
            user_id="user-1",
            headers={},
            raw_body={},
            attachments=att_infos,
        )

        # Should have synced attachment to first item
        assert message.attachment is not None
        assert message.attachment.filename == "file1.jpg"


class TestMultipleAttachmentsAsync:
    """Test async methods for multiple attachments."""

    attachment1 = {
        "id": "att-1",
        "url": "http://example.com/file1.jpg",
        "filename": "file1.jpg",
        "content_type": "image/jpeg",
        "size": 1024,
    }

    attachment2 = {
        "id": "att-2",
        "url": "http://example.com/file2.pdf",
        "filename": "file2.pdf",
        "content_type": "application/pdf",
        "size": 2048,
    }

    @pytest.mark.asyncio
    async def test_get_attachment_bytes_at(self):
        """Test downloading specific attachment by index."""
        import json
        from unittest.mock import AsyncMock

        mock_client = Mock()
        mock_client.download = AsyncMock(return_value=b"file content")

        body = {"content": "Test"}
        headers = {
            "conversationId": "conv-123",
            "attachments": json.dumps([self.attachment1, self.attachment2]),
        }

        message = Message.from_kafka_message(
            body, headers, attachment_client=mock_client
        )

        result = await message.get_attachment_bytes_at(1)

        assert result == b"file content"
        mock_client.download.assert_called_once_with(self.attachment2["url"])

    @pytest.mark.asyncio
    async def test_get_attachment_bytes_at_invalid_index(self):
        """Test error for invalid attachment index."""
        import json

        mock_client = Mock()

        body = {"content": "Test"}
        headers = {
            "conversationId": "conv-123",
            "attachments": json.dumps([self.attachment1]),
        }

        message = Message.from_kafka_message(
            body, headers, attachment_client=mock_client
        )

        with pytest.raises(ValueError, match="Invalid attachment index"):
            await message.get_attachment_bytes_at(5)

    @pytest.mark.asyncio
    async def test_get_all_attachment_bytes(self):
        """Test downloading all attachments."""
        import json
        from unittest.mock import AsyncMock

        mock_client = Mock()
        mock_client.download = AsyncMock(side_effect=[b"content1", b"content2"])

        body = {"content": "Test"}
        headers = {
            "conversationId": "conv-123",
            "attachments": json.dumps([self.attachment1, self.attachment2]),
        }

        message = Message.from_kafka_message(
            body, headers, attachment_client=mock_client
        )

        results = await message.get_all_attachment_bytes()

        assert len(results) == 2
        assert results[0] == b"content1"
        assert results[1] == b"content2"
        assert mock_client.download.call_count == 2

    @pytest.mark.asyncio
    async def test_get_all_attachment_base64(self):
        """Test getting all attachments as base64."""
        import json
        from unittest.mock import AsyncMock

        mock_client = Mock()
        mock_client.download_as_base64 = AsyncMock(
            side_effect=["base64_1", "base64_2"]
        )

        body = {"content": "Test"}
        headers = {
            "conversationId": "conv-123",
            "attachments": json.dumps([self.attachment1, self.attachment2]),
        }

        message = Message.from_kafka_message(
            body, headers, attachment_client=mock_client
        )

        results = await message.get_all_attachment_base64()

        assert len(results) == 2
        assert results[0] == "base64_1"
        assert results[1] == "base64_2"

    @pytest.mark.asyncio
    async def test_backward_compat_get_attachment_bytes(self):
        """Test backward compat: get_attachment_bytes uses first attachment."""
        import json
        from unittest.mock import AsyncMock

        mock_client = Mock()
        mock_client.download = AsyncMock(return_value=b"first file")

        body = {"content": "Test"}
        headers = {
            "conversationId": "conv-123",
            "attachments": json.dumps([self.attachment1, self.attachment2]),
        }

        message = Message.from_kafka_message(
            body, headers, attachment_client=mock_client
        )

        # Old method should use first attachment
        result = await message.get_attachment_bytes()

        assert result == b"first file"
        mock_client.download.assert_called_once_with(self.attachment1["url"])
