"""Tests for Agent Mail messaging integration."""

import os
from unittest.mock import Mock, patch

import pytest

from beads_mcp.mail import (
    MailError,
    mail_ack,
    mail_delete,
    mail_inbox,
    mail_read,
    mail_reply,
    mail_send,
)
from beads_mcp.models import (
    MailAckParams,
    MailDeleteParams,
    MailInboxParams,
    MailReadParams,
    MailReplyParams,
    MailSendParams,
)


@pytest.fixture
def mock_agent_mail_env(tmp_path):
    """Set up Agent Mail environment variables."""
    old_env = os.environ.copy()
    
    os.environ["BEADS_AGENT_MAIL_URL"] = "http://127.0.0.1:8765"
    os.environ["BEADS_AGENT_NAME"] = "test-agent"
    os.environ["BEADS_PROJECT_ID"] = str(tmp_path)
    
    yield
    
    # Restore environment
    os.environ.clear()
    os.environ.update(old_env)


@pytest.fixture
def mock_requests():
    """Mock requests library for HTTP calls."""
    with patch("beads_mcp.mail.requests.request") as mock_req:
        yield mock_req


class TestMailConfiguration:
    """Test configuration and error handling."""
    
    def test_missing_url_raises_error(self):
        """Test that missing BEADS_AGENT_MAIL_URL raises NOT_CONFIGURED."""
        old_url = os.environ.pop("BEADS_AGENT_MAIL_URL", None)
        
        try:
            with pytest.raises(MailError) as exc_info:
                mail_send(to=["alice"], subject="Test", body="Test")
            
            assert exc_info.value.code == "NOT_CONFIGURED"
            assert "BEADS_AGENT_MAIL_URL" in exc_info.value.message
        finally:
            if old_url:
                os.environ["BEADS_AGENT_MAIL_URL"] = old_url
    
    def test_missing_agent_name_derives_default(self, mock_agent_mail_env, mock_requests, tmp_path):
        """Test that missing BEADS_AGENT_NAME derives from user/repo."""
        del os.environ["BEADS_AGENT_NAME"]
        os.environ["BEADS_PROJECT_ID"] = str(tmp_path)
        
        mock_requests.return_value.status_code = 200
        mock_requests.return_value.json.return_value = {
            "deliveries": [{
                "payload": {
                    "id": 123,
                    "thread_id": "thread-1",
                }
            }]
        }
        mock_requests.return_value.content = b'{"deliveries": []}'
        
        # Should not raise - derives agent name
        result = mail_send(to=["alice"], subject="Test", body="Test")
        assert result["message_id"] == 123


class TestMailSend:
    """Test mail_send function."""
    
    def test_send_basic_message(self, mock_agent_mail_env, mock_requests):
        """Test sending a basic message."""
        mock_requests.return_value.status_code = 200
        mock_requests.return_value.json.return_value = {
            "deliveries": [{
                "payload": {
                    "id": 123,
                    "thread_id": "thread-abc",
                }
            }]
        }
        mock_requests.return_value.content = b'{"deliveries": []}'
        
        result = mail_send(
            to=["alice", "bob"],
            subject="Test Message",
            body="Hello world!",
        )
        
        assert result["message_id"] == 123
        assert result["thread_id"] == "thread-abc"
        assert result["sent_to"] == 1
        
        # Verify HTTP request
        mock_requests.assert_called_once()
        args, call_kwargs = mock_requests.call_args
        assert args[0] == "POST"
        assert call_kwargs["json"]["params"]["name"] == "send_message"
        assert call_kwargs["json"]["params"]["arguments"]["to"] == ["alice", "bob"]
        assert call_kwargs["json"]["params"]["arguments"]["subject"] == "Test Message"
    
    def test_send_urgent_message(self, mock_agent_mail_env, mock_requests):
        """Test sending urgent message."""
        mock_requests.return_value.status_code = 200
        mock_requests.return_value.json.return_value = {
            "deliveries": [{
                "payload": {"id": 456, "thread_id": "thread-xyz"}
            }]
        }
        mock_requests.return_value.content = b'{"deliveries": []}'
        
        result = mail_send(
            to=["alice"],
            subject="URGENT",
            body="Need review now!",
            urgent=True,
        )
        
        call_kwargs = mock_requests.call_args.kwargs
        assert call_kwargs["json"]["params"]["arguments"]["importance"] == "urgent"
    
    def test_send_with_cc(self, mock_agent_mail_env, mock_requests):
        """Test sending message with CC recipients."""
        mock_requests.return_value.status_code = 200
        mock_requests.return_value.json.return_value = {
            "deliveries": [{
                "payload": {"id": 789, "thread_id": "thread-123"}
            }]
        }
        mock_requests.return_value.content = b'{"deliveries": []}'
        
        result = mail_send(
            to=["alice"],
            subject="FYI",
            body="For your info",
            cc=["bob", "charlie"],
        )
        
        call_kwargs = mock_requests.call_args.kwargs
        assert call_kwargs["json"]["params"]["arguments"]["cc"] == ["bob", "charlie"]
    
    def test_send_connection_error(self, mock_agent_mail_env, mock_requests):
        """Test handling connection errors."""
        import requests.exceptions
        mock_requests.side_effect = requests.exceptions.ConnectionError("Connection refused")
        
        with pytest.raises(MailError) as exc_info:
            mail_send(to=["alice"], subject="Test", body="Test")
        
        assert exc_info.value.code == "UNAVAILABLE"
        assert "Cannot connect" in exc_info.value.message


class TestMailInbox:
    """Test mail_inbox function."""
    
    def test_fetch_inbox_default(self, mock_agent_mail_env, mock_requests):
        """Test fetching inbox with default parameters."""
        mock_requests.return_value.status_code = 200
        mock_requests.return_value.json.return_value = [
            {
                "id": 1,
                "thread_id": "thread-1",
                "from": "alice",
                "subject": "Hello",
                "created_ts": "2025-01-01T00:00:00Z",
                "read_ts": None,
                "ack_required": False,
                "importance": "normal",
                "body_md": "This is a test message",
            },
            {
                "id": 2,
                "thread_id": "thread-2",
                "from": "bob",
                "subject": "Urgent!",
                "created_ts": "2025-01-02T00:00:00Z",
                "read_ts": "2025-01-02T01:00:00Z",
                "ack_required": True,
                "importance": "urgent",
                "body_md": "Please review ASAP",
            },
        ]
        mock_requests.return_value.content = b'[]'
        
        result = mail_inbox()
        
        assert len(result["messages"]) == 2
        assert result["messages"][0]["id"] == 1
        assert result["messages"][0]["unread"] is True
        assert result["messages"][0]["urgent"] is False
        assert result["messages"][1]["id"] == 2
        assert result["messages"][1]["unread"] is False
        assert result["messages"][1]["urgent"] is True
    
    def test_fetch_inbox_unread_only(self, mock_agent_mail_env, mock_requests):
        """Test fetching only unread messages."""
        mock_requests.return_value.status_code = 200
        mock_requests.return_value.json.return_value = [
            {"id": 1, "thread_id": "t1", "from": "alice", "subject": "Test", "created_ts": "2025-01-01T00:00:00Z", "read_ts": None, "importance": "normal"},
            {"id": 2, "thread_id": "t2", "from": "bob", "subject": "Test2", "created_ts": "2025-01-01T00:00:00Z", "read_ts": "2025-01-01T01:00:00Z", "importance": "normal"},
        ]
        mock_requests.return_value.content = b'[]'
        
        result = mail_inbox(unread_only=True)
        
        # Should filter out message 2 (read)
        assert len(result["messages"]) == 1
        assert result["messages"][0]["id"] == 1
    
    def test_fetch_inbox_pagination(self, mock_agent_mail_env, mock_requests):
        """Test inbox pagination with next_cursor."""
        mock_requests.return_value.status_code = 200
        # Simulate full page (limit reached)
        mock_requests.return_value.json.return_value = [
            {"id": i, "thread_id": f"t{i}", "from": "alice", "subject": f"Msg {i}", "created_ts": "2025-01-01T00:00:00Z", "importance": "normal"}
            for i in range(20)
        ]
        mock_requests.return_value.content = b'[]'
        
        result = mail_inbox(limit=20)
        
        # Should return next_cursor when limit reached
        assert result["next_cursor"] == "19"  # Last message ID


class TestMailRead:
    """Test mail_read function."""
    
    def test_read_message_marks_read(self, mock_agent_mail_env, mock_requests):
        """Test reading message marks it as read by default."""
        # Mock resource fetch
        mock_requests.return_value.status_code = 200
        mock_requests.return_value.json.return_value = {
            "contents": [{
                "id": 123,
                "thread_id": "thread-1",
                "from": "alice",
                "to": ["test-agent"],
                "subject": "Test",
                "body_md": "Hello world!",
                "created_ts": "2025-01-01T00:00:00Z",
                "ack_required": False,
                "importance": "normal",
                "read_ts": None,
            }]
        }
        mock_requests.return_value.content = b'{}'
        
        result = mail_read(message_id=123)
        
        assert result["id"] == 123
        assert result["body"] == "Hello world!"
        assert result["urgent"] is False
        
        # Should have called both GET resource and POST mark_read
        assert mock_requests.call_count == 2
    
    def test_read_message_no_mark(self, mock_agent_mail_env, mock_requests):
        """Test reading without marking as read."""
        mock_requests.return_value.status_code = 200
        mock_requests.return_value.json.return_value = {
            "contents": [{
                "id": 123,
                "thread_id": "thread-1",
                "from": "alice",
                "to": ["test-agent"],
                "subject": "Test",
                "body_md": "Preview",
                "created_ts": "2025-01-01T00:00:00Z",
                "importance": "normal",
            }]
        }
        mock_requests.return_value.content = b'{}'
        
        result = mail_read(message_id=123, mark_read=False)
        
        # Should only call GET resource, not mark_read
        assert mock_requests.call_count == 1


class TestMailReply:
    """Test mail_reply function."""
    
    def test_reply_to_message(self, mock_agent_mail_env, mock_requests):
        """Test replying to a message."""
        mock_requests.return_value.status_code = 200
        mock_requests.return_value.json.return_value = {
            "reply": {
                "id": 456,
                "thread_id": "thread-1",
            }
        }
        mock_requests.return_value.content = b'{}'
        
        result = mail_reply(
            message_id=123,
            body="Thanks for the message!",
        )
        
        assert result["message_id"] == 456
        assert result["thread_id"] == "thread-1"
        
        call_kwargs = mock_requests.call_args.kwargs
        assert call_kwargs["json"]["params"]["name"] == "reply_message"
        assert call_kwargs["json"]["params"]["arguments"]["message_id"] == 123


class TestMailAck:
    """Test mail_ack function."""
    
    def test_acknowledge_message(self, mock_agent_mail_env, mock_requests):
        """Test acknowledging a message."""
        mock_requests.return_value.status_code = 200
        mock_requests.return_value.json.return_value = {}
        mock_requests.return_value.content = b'{}'
        
        result = mail_ack(message_id=123)
        
        assert result["acknowledged"] is True
        
        call_kwargs = mock_requests.call_args.kwargs
        assert call_kwargs["json"]["params"]["name"] == "acknowledge_message"


class TestMailDelete:
    """Test mail_delete function."""
    
    def test_delete_message(self, mock_agent_mail_env, mock_requests):
        """Test deleting/archiving a message."""
        mock_requests.return_value.status_code = 200
        mock_requests.return_value.json.return_value = {}
        mock_requests.return_value.content = b'{}'
        
        result = mail_delete(message_id=123)
        
        assert result["archived"] is True


class TestMailRetries:
    """Test retry logic and error handling."""
    
    def test_retries_on_server_error(self, mock_agent_mail_env, mock_requests):
        """Test that 500 errors trigger retries."""
        mock_requests.return_value.status_code = 500
        mock_requests.return_value.content = b'Internal Server Error'
        
        with pytest.raises(MailError) as exc_info:
            mail_send(to=["alice"], subject="Test", body="Test")
        
        assert exc_info.value.code == "UNAVAILABLE"
        # Should retry 3 times total (initial + 2 retries)
        assert mock_requests.call_count == 3
    
    def test_no_retry_on_client_error(self, mock_agent_mail_env, mock_requests):
        """Test that 404 errors don't trigger retries."""
        mock_requests.return_value.status_code = 404
        mock_requests.return_value.json.return_value = {"detail": "Not found"}
        mock_requests.return_value.content = b'{"detail": "Not found"}'
        
        with pytest.raises(MailError) as exc_info:
            mail_read(message_id=999)
        
        assert exc_info.value.code == "NOT_FOUND"
        # Should not retry on 404
        assert mock_requests.call_count == 1


class TestMailToolWrappers:
    """Test MCP tool wrappers."""
    
    def test_mail_send_params(self, mock_agent_mail_env, mock_requests):
        """Test MailSendParams validation."""
        from beads_mcp.mail_tools import beads_mail_send
        
        mock_requests.return_value.status_code = 200
        mock_requests.return_value.json.return_value = {
            "deliveries": [{
                "payload": {"id": 123, "thread_id": "t1"}
            }]
        }
        mock_requests.return_value.content = b'{}'
        
        params = MailSendParams(
            to=["alice"],
            subject="Test",
            body="Hello",
            urgent=True,
        )
        
        result = beads_mail_send(params)
        assert result["message_id"] == 123
    
    def test_mail_inbox_default_params(self, mock_agent_mail_env, mock_requests):
        """Test MailInboxParams with defaults."""
        from beads_mcp.mail_tools import beads_mail_inbox
        
        mock_requests.return_value.status_code = 200
        mock_requests.return_value.json.return_value = []
        mock_requests.return_value.content = b'[]'
        
        params = MailInboxParams()  # All defaults
        result = beads_mail_inbox(params)
        
        assert result["messages"] == []
        assert result["next_cursor"] is None
