"""Unit tests for PurgeQueueOperation (Story 3.4 Task 2-6)."""

from unittest.mock import AsyncMock, MagicMock, patch

import httpx
import pytest

from rabbitmq_mcp_connection.http_client import HTTPClientManager
from rabbitmq_mcp_connection.operations.topology import PurgeQueueOperation
from rabbitmq_mcp_connection.schemas.topology import PurgeQueueRequest


@pytest.fixture
def mock_http_client():
    """Create mock HTTPClientManager for testing."""
    client = MagicMock(spec=HTTPClientManager)
    client.request = AsyncMock()
    return client


@pytest.fixture
def purge_operation(mock_http_client):
    """Create PurgeQueueOperation instance with mock HTTP client."""
    return PurgeQueueOperation(http_client=mock_http_client)


class TestPurgeQueueOperationSuccess:
    """Test successful purge queue operations (AC #1, #4, #6)."""

    @pytest.mark.asyncio
    async def test_purge_queue_with_messages(self, purge_operation, mock_http_client):
        """Test purging queue with messages returns correct count (AC #1, #4)."""
        request = PurgeQueueRequest(name="orders", vhost="/")

        # Mock 200 OK response with message_count
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"message_count": 500}
        mock_http_client.request.return_value = mock_response

        result = await purge_operation.execute(request, correlation_id="test-123")

        # Verify correct endpoint called (AC #1, Task 3)
        mock_http_client.request.assert_called_once_with(
            method="DELETE",
            path="/api/queues/%2F/orders/contents",
        )

        # Verify response structure (AC #4)
        assert result["status"] == "success"
        assert result["purged_message_count"] == 500
        assert result["queue_name"] == "orders"
        assert result["vhost"] == "/"

    @pytest.mark.asyncio
    async def test_purge_empty_queue(self, purge_operation, mock_http_client):
        """Test purging empty queue returns success with count 0 (AC #6)."""
        request = PurgeQueueRequest(name="temp-queue", vhost="/")

        # Mock 200 OK response with message_count=0
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"message_count": 0}
        mock_http_client.request.return_value = mock_response

        result = await purge_operation.execute(request)

        # Verify success with zero messages (AC #6)
        assert result["status"] == "success"
        assert result["purged_message_count"] == 0
        assert result["queue_name"] == "temp-queue"

    @pytest.mark.asyncio
    @pytest.mark.parametrize(
        "message_count",
        [1, 10, 100, 500, 1000, 10000],
    )
    async def test_purge_various_message_counts(
        self, purge_operation, mock_http_client, message_count
    ):
        """Test purging queues with various message counts."""
        request = PurgeQueueRequest(name="test-queue", vhost="/")

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"message_count": message_count}
        mock_http_client.request.return_value = mock_response

        result = await purge_operation.execute(request)

        assert result["purged_message_count"] == message_count

    @pytest.mark.asyncio
    async def test_purge_custom_vhost(self, purge_operation, mock_http_client):
        """Test purging queue in custom vhost with URL encoding."""
        request = PurgeQueueRequest(name="tasks", vhost="/prod")

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"message_count": 42}
        mock_http_client.request.return_value = mock_response

        result = await purge_operation.execute(request)

        # Verify vhost URL encoded correctly
        mock_http_client.request.assert_called_once_with(
            method="DELETE",
            path="/api/queues/%2Fprod/tasks/contents",
        )

        assert result["vhost"] == "/prod"


class TestPurgeQueueOperationErrors:
    """Test error handling in purge operations (AC #7)."""

    @pytest.mark.asyncio
    async def test_purge_nonexistent_queue(self, purge_operation, mock_http_client):
        """Test purging non-existent queue raises ValueError (AC #7)."""
        request = PurgeQueueRequest(name="missing-queue", vhost="/")

        # Mock 404 Not Found response
        mock_response = MagicMock()
        mock_response.status_code = 404
        mock_http_client.request.return_value = mock_response

        with pytest.raises(ValueError) as exc_info:
            await purge_operation.execute(request)

        assert "Queue 'missing-queue' not found" in str(exc_info.value)
        assert "vhost '/'" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_purge_timeout(self, purge_operation, mock_http_client):
        """Test purge operation timeout raises TimeoutException."""
        request = PurgeQueueRequest(name="slow-queue", vhost="/")

        # Mock timeout exception
        mock_http_client.request.side_effect = httpx.TimeoutException("Request timeout")

        with pytest.raises(httpx.TimeoutException):
            await purge_operation.execute(request)

    @pytest.mark.asyncio
    async def test_purge_unexpected_status_code(
        self, purge_operation, mock_http_client
    ):
        """Test purge with unexpected status code raises ValueError."""
        request = PurgeQueueRequest(name="test-queue", vhost="/")

        # Mock 500 Internal Server Error
        mock_response = MagicMock()
        mock_response.status_code = 500
        mock_http_client.request.return_value = mock_response

        with pytest.raises(ValueError) as exc_info:
            await purge_operation.execute(request)

        assert "Failed to purge queue" in str(exc_info.value)
        assert "500" in str(exc_info.value)


class TestPurgeQueueOperationEndpoint:
    """Test correct endpoint usage (AC #2, #3, Task 3)."""

    @pytest.mark.asyncio
    async def test_purge_uses_contents_endpoint(
        self, purge_operation, mock_http_client
    ):
        """Test purge uses /contents endpoint, not queue deletion endpoint."""
        request = PurgeQueueRequest(name="orders", vhost="/")

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"message_count": 100}
        mock_http_client.request.return_value = mock_response

        await purge_operation.execute(request)

        # Verify /contents suffix is used (AC #2, #3)
        call_args = mock_http_client.request.call_args
        assert "/contents" in call_args.kwargs["path"]
        assert not call_args.kwargs["path"].endswith("/orders")

    @pytest.mark.asyncio
    async def test_purge_url_encoding(self, purge_operation, mock_http_client):
        """Test URL encoding for vhost and queue name."""
        request = PurgeQueueRequest(name="test-queue", vhost="/")

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"message_count": 0}
        mock_http_client.request.return_value = mock_response

        await purge_operation.execute(request)

        # Verify vhost "/" is encoded as "%2F"
        call_args = mock_http_client.request.call_args
        assert "%2F" in call_args.kwargs["path"]


class TestPurgeQueueOperationResponseParsing:
    """Test response body parsing (AC #4, Task 2)."""

    @pytest.mark.asyncio
    async def test_parse_message_count_from_response(
        self, purge_operation, mock_http_client
    ):
        """Test extracting message_count from response body."""
        request = PurgeQueueRequest(name="orders", vhost="/")

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"message_count": 500}
        mock_http_client.request.return_value = mock_response

        result = await purge_operation.execute(request)

        assert result["purged_message_count"] == 500

    @pytest.mark.asyncio
    async def test_missing_message_count_defaults_to_zero(
        self, purge_operation, mock_http_client
    ):
        """Test response without message_count field defaults to 0."""
        request = PurgeQueueRequest(name="test-queue", vhost="/")

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {}  # No message_count field
        mock_http_client.request.return_value = mock_response

        result = await purge_operation.execute(request)

        # Should default to 0 if field missing
        assert result["purged_message_count"] == 0

    @pytest.mark.asyncio
    async def test_extra_response_fields_ignored(
        self, purge_operation, mock_http_client
    ):
        """Test extra fields in response are ignored (forward compatibility)."""
        request = PurgeQueueRequest(name="test-queue", vhost="/")

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "message_count": 100,
            "extra_field": "ignored",
            "future_feature": True,
        }
        mock_http_client.request.return_value = mock_response

        result = await purge_operation.execute(request)

        # Only expected fields should be present
        assert result["purged_message_count"] == 100
        assert "extra_field" not in result
        assert "future_feature" not in result


class TestPurgeQueueOperationLogging:
    """Test structured logging with correlation IDs (AC #8, Task 5)."""

    @pytest.mark.asyncio
    @patch("rabbitmq_mcp_connection.operations.topology.logger")
    async def test_log_purge_start(
        self, mock_logger, purge_operation, mock_http_client
    ):
        """Test purge start event is logged with correlation_id."""
        request = PurgeQueueRequest(name="orders", vhost="/")

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"message_count": 100}
        mock_http_client.request.return_value = mock_response

        await purge_operation.execute(request, correlation_id="test-corr-123")

        # Verify start log entry
        start_call = mock_logger.info.call_args_list[0]
        assert start_call[0][0] == "queue.purge.start"
        assert start_call[1]["name"] == "orders"
        assert start_call[1]["vhost"] == "/"
        assert start_call[1]["correlation_id"] == "test-corr-123"

    @pytest.mark.asyncio
    @patch("rabbitmq_mcp_connection.operations.topology.logger")
    async def test_log_purge_success(
        self, mock_logger, purge_operation, mock_http_client
    ):
        """Test successful purge is logged with count and correlation_id."""
        request = PurgeQueueRequest(name="orders", vhost="/")

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"message_count": 500}
        mock_http_client.request.return_value = mock_response

        await purge_operation.execute(request, correlation_id="test-corr-456")

        # Verify success log entry
        success_call = mock_logger.info.call_args_list[1]
        assert success_call[0][0] == "queue.purge.success"
        assert success_call[1]["purged_count"] == 500
        assert success_call[1]["correlation_id"] == "test-corr-456"

    @pytest.mark.asyncio
    @patch("rabbitmq_mcp_connection.operations.topology.logger")
    async def test_log_purge_empty(
        self, mock_logger, purge_operation, mock_http_client
    ):
        """Test empty queue purge is logged separately (AC #6)."""
        request = PurgeQueueRequest(name="empty-queue", vhost="/")

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"message_count": 0}
        mock_http_client.request.return_value = mock_response

        await purge_operation.execute(request, correlation_id="test-empty-123")

        # Verify empty queue log entry
        empty_call = mock_logger.info.call_args_list[1]
        assert empty_call[0][0] == "queue.purge.empty"
        assert empty_call[1]["correlation_id"] == "test-empty-123"

    @pytest.mark.asyncio
    @patch("rabbitmq_mcp_connection.operations.topology.logger")
    async def test_log_purge_not_found(
        self, mock_logger, purge_operation, mock_http_client
    ):
        """Test 404 error is logged with correlation_id."""
        request = PurgeQueueRequest(name="missing", vhost="/")

        mock_response = MagicMock()
        mock_response.status_code = 404
        mock_http_client.request.return_value = mock_response

        with pytest.raises(ValueError):
            await purge_operation.execute(request, correlation_id="test-404-123")

        # Verify error log entry
        error_call = mock_logger.error.call_args
        assert error_call[0][0] == "queue.purge.not_found"
        assert error_call[1]["correlation_id"] == "test-404-123"

    @pytest.mark.asyncio
    @patch("rabbitmq_mcp_connection.operations.topology.logger")
    async def test_log_correlation_id_propagation(
        self, mock_logger, purge_operation, mock_http_client
    ):
        """Test correlation_id is present in all log entries."""
        request = PurgeQueueRequest(name="test-queue", vhost="/")

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"message_count": 42}
        mock_http_client.request.return_value = mock_response

        await purge_operation.execute(request, correlation_id="propagate-123")

        # Verify all log calls include correlation_id
        for call in mock_logger.info.call_args_list:
            assert call[1]["correlation_id"] == "propagate-123"


class TestPurgeQueueOperationPerformance:
    """Test performance monitoring and warnings (AC #5, Task 6)."""

    @pytest.mark.asyncio
    @patch("rabbitmq_mcp_connection.operations.topology.logger")
    async def test_log_slow_purge_warning(
        self, mock_logger, purge_operation, mock_http_client
    ):
        """Test slow purge (>500ms) logs warning (AC #5)."""
        request = PurgeQueueRequest(name="slow-queue", vhost="/")

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"message_count": 10000}
        mock_http_client.request.return_value = mock_response

        # Use a slow async call to simulate delay
        async def slow_request(*args, **kwargs):
            import asyncio

            await asyncio.sleep(0.6)  # Simulate 600ms
            return mock_response

        mock_http_client.request = slow_request

        await purge_operation.execute(request)

        # Verify slow operation warning logged
        warning_calls = [
            call
            for call in mock_logger.warning.call_args_list
            if call[0][0] == "queue.purge.slow"
        ]
        assert len(warning_calls) > 0
        warning_call = warning_calls[0]
        assert warning_call[1]["threshold_ms"] == 500
        assert warning_call[1]["purged_count"] == 10000

    @pytest.mark.asyncio
    @patch("rabbitmq_mcp_connection.operations.topology.logger")
    async def test_fast_purge_no_warning(
        self, mock_logger, purge_operation, mock_http_client
    ):
        """Test fast purge (<500ms) does not log warning."""
        request = PurgeQueueRequest(name="fast-queue", vhost="/")

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"message_count": 50}
        mock_http_client.request.return_value = mock_response

        await purge_operation.execute(request)

        # Verify no warning logged
        warning_calls = [
            call
            for call in mock_logger.warning.call_args_list
            if call[0][0] == "queue.purge.slow"
        ]
        assert len(warning_calls) == 0
