"""Tests for MRP MCP server module."""

from __future__ import annotations

import asyncio
from datetime import datetime, timezone
from unittest.mock import AsyncMock, MagicMock, patch, PropertyMock

import pytest

from mrp.models import Message, PollResult
from mrp_mcp.config import Config
from mrp_mcp.server import background_poller, create_server
from mrp_mcp.state import SessionState


def _now():
    return datetime.now(timezone.utc)


def _make_message(msg_id="msg_001"):
    return Message(
        message_id=msg_id,
        sender_key="sender-pk-abcdefghijklmnopqrstuvwxyz1234",
        recipient_key="test-pk-1234567890abcdefghijklmnopqrstuv",
        content_type="application/json",
        body={"text": "Hello!"},
        attachments=[],
        status="sent",
        thread_id=None,
        in_reply_to=None,
        metadata={},
        created_at=_now(),
        expires_at=_now(),
    )


class TestBackgroundPoller:
    @pytest.mark.asyncio
    async def test_background_poller_no_messages(self):
        agent = MagicMock()
        agent.client.poll_messages.return_value = PollResult(messages=[], next_cursor=None)
        state = SessionState()

        with patch("mrp_mcp.server.asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
            mock_sleep.side_effect = [None, asyncio.CancelledError()]
            try:
                await background_poller(agent, state, 5.0)
            except asyncio.CancelledError:
                pass

        assert state.unread == []
        assert state.last_poll_cursor is None

    @pytest.mark.asyncio
    async def test_background_poller_with_messages(self):
        messages = [_make_message("msg_001"), _make_message("msg_002")]
        agent = MagicMock()
        agent.client.poll_messages.return_value = PollResult(
            messages=messages, next_cursor="cursor-abc"
        )
        state = SessionState()

        with patch("mrp_mcp.server.asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
            mock_sleep.side_effect = [None, asyncio.CancelledError()]
            with patch("mrp_mcp.server.asyncio.to_thread", new_callable=AsyncMock) as mock_to_thread:
                mock_to_thread.return_value = PollResult(
                    messages=messages, next_cursor="cursor-abc"
                )
                try:
                    await background_poller(agent, state, 5.0)
                except asyncio.CancelledError:
                    pass

        assert len(state.unread) == 2
        assert state.last_poll_cursor == "cursor-abc"

    @pytest.mark.asyncio
    async def test_background_poller_updates_cursor(self):
        agent = MagicMock()
        state = SessionState()
        state.last_poll_cursor = "old-cursor"

        with patch("mrp_mcp.server.asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
            mock_sleep.side_effect = [None, asyncio.CancelledError()]
            with patch("mrp_mcp.server.asyncio.to_thread", new_callable=AsyncMock) as mock_to_thread:
                mock_to_thread.return_value = PollResult(
                    messages=[_make_message()], next_cursor="new-cursor"
                )
                try:
                    await background_poller(agent, state, 5.0)
                except asyncio.CancelledError:
                    pass

        assert state.last_poll_cursor == "new-cursor"

    @pytest.mark.asyncio
    async def test_background_poller_exception_handling(self):
        agent = MagicMock()
        state = SessionState()

        with patch("mrp_mcp.server.asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
            mock_sleep.side_effect = [None, asyncio.CancelledError()]
            with patch("mrp_mcp.server.asyncio.to_thread", new_callable=AsyncMock) as mock_to_thread:
                mock_to_thread.side_effect = RuntimeError("Network error")
                try:
                    await background_poller(agent, state, 5.0)
                except asyncio.CancelledError:
                    pass

        # Should silently handle exception and continue
        assert state.unread == []
        assert state.last_poll_cursor is None


class TestCreateServer:
    @patch("mrp_mcp.server.agent_tools")
    @patch("mrp_mcp.server.blobs")
    @patch("mrp_mcp.server.threads")
    @patch("mrp_mcp.server.messaging")
    @patch("mrp_mcp.server.discovery")
    @patch("mrp_mcp.server.Agent")
    def test_create_server_auto_register(
        self, mock_agent_cls, mock_disc, mock_msg, mock_thr, mock_blobs, mock_at
    ):
        mock_agent = MagicMock()
        mock_agent_cls.return_value = mock_agent

        config = Config(
            relay_url="http://localhost:8080",
            key_file="/tmp/test.key",
            agent_name="TestBot",
            auto_register=True,
        )
        mcp = create_server(config)

        mock_agent.register.assert_called_once()
        assert mcp is not None

    @patch("mrp_mcp.server.agent_tools")
    @patch("mrp_mcp.server.blobs")
    @patch("mrp_mcp.server.threads")
    @patch("mrp_mcp.server.messaging")
    @patch("mrp_mcp.server.discovery")
    @patch("mrp_mcp.server.Agent")
    def test_create_server_no_register(
        self, mock_agent_cls, mock_disc, mock_msg, mock_thr, mock_blobs, mock_at
    ):
        mock_agent = MagicMock()
        mock_agent_cls.return_value = mock_agent

        config = Config(
            relay_url="http://localhost:8080",
            auto_register=False,
        )
        mcp = create_server(config)

        mock_agent.register.assert_not_called()
        assert mcp is not None
