"""
Copyright (C) 2025 Luciano Guerche

This file is part of rabbitmq-mcp-server.

rabbitmq-mcp-server is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

rabbitmq-mcp-server is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with rabbitmq-mcp-server. If not, see <https://www.gnu.org/licenses/>.
"""

"""Unit tests for ListExchangesOperation (Story 3.5)."""

from unittest.mock import AsyncMock, Mock

import httpx
import pytest

from rabbitmq_mcp_connection.operations.topology import ListExchangesOperation
from rabbitmq_mcp_connection.schemas.topology import (
    ExchangeInfo,
    ListExchangesRequest,
    MessageStats,
)


@pytest.fixture
def mock_http_client():
    """Mock HTTPClientManager for testing."""
    client = Mock()
    client.request = AsyncMock()
    return client


@pytest.fixture
def list_exchanges_operation(mock_http_client):
    """Create ListExchangesOperation instance with mocked client."""
    return ListExchangesOperation(mock_http_client)


@pytest.fixture
def sample_exchanges_data():
    """Sample exchange data from RabbitMQ API."""
    return [
        {
            "name": "amq.direct",
            "vhost": "/",
            "type": "direct",
            "durable": True,
            "auto_delete": False,
            "internal": False,
            "arguments": {},
        },
        {
            "name": "amq.topic",
            "vhost": "/",
            "type": "topic",
            "durable": True,
            "auto_delete": False,
            "internal": False,
            "arguments": {},
        },
        {
            "name": "orders",
            "vhost": "/",
            "type": "direct",
            "durable": True,
            "auto_delete": False,
            "internal": False,
            "arguments": {},
            "message_stats": {
                "publish_in": 10.5,
                "publish_out": 9.8,
            },
        },
        {
            "name": "",  # Default exchange
            "vhost": "/",
            "type": "direct",
            "durable": True,
            "auto_delete": False,
            "internal": False,
            "arguments": {},
        },
    ]


# =====================================================================
# Schema Validation Tests (~15 tests)
# =====================================================================


class TestListExchangesRequestSchema:
    """Test ListExchangesRequest schema validation."""

    def test_default_vhost(self):
        """Test default vhost is '/'."""
        request = ListExchangesRequest()
        assert request.vhost == "/"
        assert request.sort_by == "name"

    def test_custom_vhost(self):
        """Test custom vhost."""
        request = ListExchangesRequest(vhost="/prod")
        assert request.vhost == "/prod"

    def test_empty_vhost_raises_error(self):
        """Test empty vhost raises ValueError."""
        with pytest.raises(ValueError, match="Virtual host name cannot be empty"):
            ListExchangesRequest(vhost="")

    def test_name_pattern_filter(self):
        """Test name_pattern filter is optional."""
        request = ListExchangesRequest(name_pattern="^amq\\..*")
        assert request.name_pattern == "^amq\\..*"

    def test_type_filter_valid(self):
        """Test valid type filter values."""
        for exchange_type in ["direct", "topic", "fanout", "headers"]:
            request = ListExchangesRequest(type=exchange_type)
            assert request.type == exchange_type

    def test_type_filter_invalid(self):
        """Test invalid type filter raises validation error."""
        with pytest.raises(ValueError):
            ListExchangesRequest(type="invalid")

    def test_sort_by_valid_values(self):
        """Test valid sort_by values."""
        for sort_field in ["name", "type", "message_in_rate"]:
            request = ListExchangesRequest(sort_by=sort_field)
            assert request.sort_by == sort_field

    def test_sort_by_invalid(self):
        """Test invalid sort_by raises validation error."""
        with pytest.raises(ValueError):
            ListExchangesRequest(sort_by="invalid_field")

    def test_combined_filters(self):
        """Test combining name_pattern and type filters."""
        request = ListExchangesRequest(
            vhost="/prod",
            name_pattern="^orders",
            type="topic",
            sort_by="message_in_rate",
        )
        assert request.vhost == "/prod"
        assert request.name_pattern == "^orders"
        assert request.type == "topic"
        assert request.sort_by == "message_in_rate"

    def test_extra_fields_ignored(self):
        """Test extra fields are ignored per model config."""
        # Pydantic should ignore unknown fields
        request = ListExchangesRequest(**{"vhost": "/", "unknown_field": "ignored"})
        assert request.vhost == "/"
        assert not hasattr(request, "unknown_field")


# =====================================================================
# Response Model Tests (~10 tests)
# =====================================================================


class TestExchangeInfoSchema:
    """Test ExchangeInfo response model."""

    def test_parse_direct_exchange(self):
        """Test parsing direct exchange properties."""
        data = {
            "name": "orders",
            "vhost": "/",
            "type": "direct",
            "durable": True,
            "auto_delete": False,
            "internal": False,
            "arguments": {},
        }
        exchange = ExchangeInfo(**data)
        assert exchange.name == "orders"
        assert exchange.type == "direct"
        assert exchange.durable is True
        assert exchange.internal is False

    def test_parse_topic_exchange(self):
        """Test parsing topic exchange properties."""
        data = {
            "name": "logs",
            "vhost": "/",
            "type": "topic",
            "durable": True,
            "auto_delete": False,
            "internal": False,
            "arguments": {},
        }
        exchange = ExchangeInfo(**data)
        assert exchange.type == "topic"

    def test_parse_fanout_exchange(self):
        """Test parsing fanout exchange properties."""
        data = {
            "name": "broadcast",
            "vhost": "/",
            "type": "fanout",
            "durable": False,
            "auto_delete": True,
            "internal": False,
            "arguments": {},
        }
        exchange = ExchangeInfo(**data)
        assert exchange.type == "fanout"
        assert exchange.auto_delete is True

    def test_parse_headers_exchange(self):
        """Test parsing headers exchange properties."""
        data = {
            "name": "routing",
            "vhost": "/",
            "type": "headers",
            "durable": True,
            "auto_delete": False,
            "internal": False,
            "arguments": {"x-match": "all"},
        }
        exchange = ExchangeInfo(**data)
        assert exchange.type == "headers"
        assert exchange.arguments == {"x-match": "all"}

    def test_parse_system_exchange(self):
        """Test parsing system exchange (amq.*)."""
        data = {
            "name": "amq.direct",
            "vhost": "/",
            "type": "direct",
            "durable": True,
            "auto_delete": False,
            "internal": False,
            "arguments": {},
        }
        exchange = ExchangeInfo(**data)
        assert exchange.name.startswith("amq.")

    def test_parse_default_exchange(self):
        """Test parsing default exchange (empty string name)."""
        data = {
            "name": "",
            "vhost": "/",
            "type": "direct",
            "durable": True,
            "auto_delete": False,
            "internal": False,
            "arguments": {},
        }
        exchange = ExchangeInfo(**data)
        assert exchange.name == ""

    def test_parse_with_message_stats(self):
        """Test parsing exchange with message_stats."""
        data = {
            "name": "orders",
            "vhost": "/",
            "type": "direct",
            "durable": True,
            "auto_delete": False,
            "internal": False,
            "arguments": {},
            "message_stats": {
                "publish_in": 10.5,
                "publish_out": 9.8,
            },
        }
        exchange = ExchangeInfo(**data)
        assert exchange.message_stats is not None
        assert exchange.message_stats.publish_in == 10.5
        assert exchange.message_stats.publish_out == 9.8

    def test_parse_without_message_stats(self):
        """Test parsing exchange without message_stats (optional field)."""
        data = {
            "name": "idle",
            "vhost": "/",
            "type": "direct",
            "durable": True,
            "auto_delete": False,
            "internal": False,
            "arguments": {},
        }
        exchange = ExchangeInfo(**data)
        assert exchange.message_stats is None

    def test_invalid_type_raises_error(self):
        """Test invalid exchange type raises validation error."""
        data = {
            "name": "test",
            "vhost": "/",
            "type": "invalid_type",
            "durable": True,
            "auto_delete": False,
            "internal": False,
            "arguments": {},
        }
        from pydantic import ValidationError

        with pytest.raises(ValidationError):
            ExchangeInfo(**data)

    def test_extra_fields_ignored(self):
        """Test extra fields ignored for forward compatibility."""
        data = {
            "name": "test",
            "vhost": "/",
            "type": "direct",
            "durable": True,
            "auto_delete": False,
            "internal": False,
            "arguments": {},
            "future_field": "ignored",
        }
        exchange = ExchangeInfo(**data)
        assert exchange.name == "test"


# =====================================================================
# Successful List Tests (~8 scenarios)
# =====================================================================


class TestListExchangesSuccess:
    """Test successful list exchanges operations."""

    @pytest.mark.asyncio
    async def test_empty_vhost_returns_empty_list(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test empty vhost returns empty list (AC #5)."""
        # Arrange
        request = ListExchangesRequest(vhost="/empty")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = []
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        assert result == []
        mock_http_client.request.assert_called_once_with(
            method="GET",
            path="/api/exchanges/%2Fempty",
        )

    @pytest.mark.asyncio
    async def test_list_single_exchange(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test listing vhost with 1 exchange."""
        # Arrange
        request = ListExchangesRequest(vhost="/")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "name": "orders",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            }
        ]
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        assert len(result) == 1
        assert result[0].name == "orders"
        assert result[0].type == "direct"

    @pytest.mark.asyncio
    async def test_list_multiple_exchanges(
        self, list_exchanges_operation, mock_http_client, sample_exchanges_data
    ):
        """Test listing vhost with multiple exchanges."""
        # Arrange
        request = ListExchangesRequest(vhost="/")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = sample_exchanges_data
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        assert len(result) == 4
        exchange_names = [ex.name for ex in result]
        assert "amq.direct" in exchange_names
        assert "orders" in exchange_names

    @pytest.mark.asyncio
    async def test_includes_system_exchanges(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test system exchanges (amq.*) included in results (AC #1)."""
        # Arrange
        request = ListExchangesRequest(vhost="/")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "name": "amq.direct",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            },
            {
                "name": "amq.topic",
                "vhost": "/",
                "type": "topic",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            },
        ]
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        assert len(result) == 2
        assert all(ex.name.startswith("amq.") for ex in result)

    @pytest.mark.asyncio
    async def test_includes_default_exchange(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test default exchange (empty string) included in results (AC #1)."""
        # Arrange
        request = ListExchangesRequest(vhost="/")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "name": "",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            }
        ]
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        assert len(result) == 1
        assert result[0].name == ""

    @pytest.mark.asyncio
    async def test_custom_vhost_url_encoded(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test custom vhost is URL-encoded in request."""
        # Arrange
        request = ListExchangesRequest(vhost="/prod")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = []
        mock_http_client.request.return_value = mock_response

        # Act
        await list_exchanges_operation.execute(request)

        # Assert
        mock_http_client.request.assert_called_once_with(
            method="GET",
            path="/api/exchanges/%2Fprod",
        )

    @pytest.mark.asyncio
    async def test_message_stats_included_when_available(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test message_stats included when available (AC #3)."""
        # Arrange
        request = ListExchangesRequest(vhost="/")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "name": "orders",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
                "message_stats": {
                    "publish_in": 15.2,
                    "publish_out": 14.8,
                },
            }
        ]
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        assert len(result) == 1
        assert result[0].message_stats is not None
        assert result[0].message_stats.publish_in == 15.2

    @pytest.mark.asyncio
    async def test_correlation_id_in_logs(
        self, list_exchanges_operation, mock_http_client, caplog
    ):
        """Test correlation_id present in logs."""
        # Arrange
        request = ListExchangesRequest(vhost="/")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = []
        mock_http_client.request.return_value = mock_response

        # Act
        await list_exchanges_operation.execute(request, correlation_id="test-123")

        # Assert
        # Logs would contain correlation_id in structured logging
        # This is more of an integration test, but we verify the parameter is passed


# =====================================================================
# Error Handling Tests (~5 scenarios)
# =====================================================================


class TestListExchangesErrors:
    """Test error handling for list exchanges operation."""

    @pytest.mark.asyncio
    async def test_vhost_not_found_404(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test 404 raises ValueError for non-existent vhost (AC #6)."""
        # Arrange
        request = ListExchangesRequest(vhost="/invalid")
        mock_response = Mock()
        mock_response.status_code = 404
        mock_http_client.request.return_value = mock_response

        # Act & Assert
        with pytest.raises(ValueError, match="Virtual host '/invalid' does not exist"):
            await list_exchanges_operation.execute(request)

    @pytest.mark.asyncio
    async def test_timeout_exception(self, list_exchanges_operation, mock_http_client):
        """Test timeout raises TimeoutException."""
        # Arrange
        request = ListExchangesRequest(vhost="/")
        mock_http_client.request.side_effect = httpx.TimeoutException("Timeout")

        # Act & Assert
        with pytest.raises(httpx.TimeoutException):
            await list_exchanges_operation.execute(request)

    @pytest.mark.asyncio
    async def test_unexpected_status_code(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test unexpected status code raises ValueError."""
        # Arrange
        request = ListExchangesRequest(vhost="/")
        mock_response = Mock()
        mock_response.status_code = 500
        mock_http_client.request.return_value = mock_response

        # Act & Assert
        with pytest.raises(ValueError, match="Failed to list exchanges: HTTP 500"):
            await list_exchanges_operation.execute(request)

    @pytest.mark.asyncio
    async def test_malformed_json_response(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test malformed JSON response raises error."""
        # Arrange
        request = ListExchangesRequest(vhost="/")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.side_effect = ValueError("Invalid JSON")
        mock_http_client.request.return_value = mock_response

        # Act & Assert
        with pytest.raises(ValueError):
            await list_exchanges_operation.execute(request)

    @pytest.mark.asyncio
    async def test_invalid_exchange_data(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test invalid exchange data raises validation error."""
        # Arrange
        request = ListExchangesRequest(vhost="/")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "name": "test",
                "vhost": "/",
                "type": "invalid_type",  # Invalid type
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            }
        ]
        mock_http_client.request.return_value = mock_response

        # Act & Assert
        with pytest.raises(ValueError):
            await list_exchanges_operation.execute(request)


# =====================================================================
# Filtering Tests (~8 scenarios)
# =====================================================================


class TestExchangeFiltering:
    """Test client-side filtering logic (AC #7, Task 4)."""

    @pytest.mark.asyncio
    async def test_filter_by_name_pattern(
        self, list_exchanges_operation, mock_http_client, sample_exchanges_data
    ):
        """Test filtering by name pattern (regex)."""
        # Arrange
        request = ListExchangesRequest(vhost="/", name_pattern="^amq\\..*")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = sample_exchanges_data
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        assert len(result) == 2
        assert all(ex.name.startswith("amq.") for ex in result)

    @pytest.mark.asyncio
    async def test_filter_by_type_direct(
        self, list_exchanges_operation, mock_http_client, sample_exchanges_data
    ):
        """Test filtering by type (direct)."""
        # Arrange
        request = ListExchangesRequest(vhost="/", type="direct")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = sample_exchanges_data
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        assert len(result) == 3  # amq.direct, orders, default exchange
        assert all(ex.type == "direct" for ex in result)

    @pytest.mark.asyncio
    async def test_filter_by_type_topic(
        self, list_exchanges_operation, mock_http_client, sample_exchanges_data
    ):
        """Test filtering by type (topic)."""
        # Arrange
        request = ListExchangesRequest(vhost="/", type="topic")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = sample_exchanges_data
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        assert len(result) == 1
        assert result[0].name == "amq.topic"

    @pytest.mark.asyncio
    async def test_combined_filters(
        self, list_exchanges_operation, mock_http_client, sample_exchanges_data
    ):
        """Test combined name_pattern and type filters."""
        # Arrange
        request = ListExchangesRequest(
            vhost="/", name_pattern="^amq\\..*", type="topic"
        )
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = sample_exchanges_data
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        assert len(result) == 1
        assert result[0].name == "amq.topic"

    @pytest.mark.asyncio
    async def test_no_matches_returns_empty_list(
        self, list_exchanges_operation, mock_http_client, sample_exchanges_data
    ):
        """Test no matches after filtering returns empty list."""
        # Arrange
        request = ListExchangesRequest(vhost="/", name_pattern="^nonexistent")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = sample_exchanges_data
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        assert result == []

    @pytest.mark.asyncio
    async def test_invalid_regex_pattern(
        self, list_exchanges_operation, mock_http_client, sample_exchanges_data
    ):
        """Test invalid regex pattern raises ValueError."""
        # Arrange
        request = ListExchangesRequest(vhost="/", name_pattern="[invalid(")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = sample_exchanges_data
        mock_http_client.request.return_value = mock_response

        # Act & Assert
        with pytest.raises(ValueError, match="Invalid regex pattern"):
            await list_exchanges_operation.execute(request)

    @pytest.mark.asyncio
    async def test_filter_default_exchange_by_empty_name(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test filtering for default exchange (empty name)."""
        # Arrange
        request = ListExchangesRequest(vhost="/", name_pattern="^$")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "name": "",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            },
            {
                "name": "orders",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            },
        ]
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        assert len(result) == 1
        assert result[0].name == ""

    @pytest.mark.asyncio
    async def test_filter_by_type_fanout(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test filtering by type (fanout)."""
        # Arrange
        request = ListExchangesRequest(vhost="/", type="fanout")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "name": "broadcast",
                "vhost": "/",
                "type": "fanout",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            }
        ]
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        assert len(result) == 1
        assert result[0].type == "fanout"


# =====================================================================
# Sorting Tests (~6 scenarios)
# =====================================================================


class TestExchangeSorting:
    """Test client-side sorting logic (AC #8, Task 5)."""

    @pytest.mark.asyncio
    async def test_sort_by_name_default(
        self, list_exchanges_operation, mock_http_client, sample_exchanges_data
    ):
        """Test default sort by name (alphabetical, case-insensitive)."""
        # Arrange
        request = ListExchangesRequest(vhost="/")  # Default sort_by="name"
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = sample_exchanges_data
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        names = [ex.name for ex in result]
        assert names == ["", "amq.direct", "amq.topic", "orders"]

    @pytest.mark.asyncio
    async def test_sort_by_type(self, list_exchanges_operation, mock_http_client):
        """Test sort by type (alphabetical)."""
        # Arrange
        request = ListExchangesRequest(vhost="/", sort_by="type")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "name": "test1",
                "vhost": "/",
                "type": "topic",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            },
            {
                "name": "test2",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            },
            {
                "name": "test3",
                "vhost": "/",
                "type": "fanout",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            },
        ]
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        types = [ex.type for ex in result]
        assert types == ["direct", "fanout", "topic"]

    @pytest.mark.asyncio
    async def test_sort_by_message_in_rate_descending(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test sort by message_in_rate (descending, highest first)."""
        # Arrange
        request = ListExchangesRequest(vhost="/", sort_by="message_in_rate")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "name": "low",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
                "message_stats": {"publish_in": 5.0, "publish_out": 4.5},
            },
            {
                "name": "high",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
                "message_stats": {"publish_in": 20.0, "publish_out": 19.5},
            },
            {
                "name": "medium",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
                "message_stats": {"publish_in": 10.0, "publish_out": 9.5},
            },
        ]
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        names = [ex.name for ex in result]
        assert names == ["high", "medium", "low"]

    @pytest.mark.asyncio
    async def test_sort_with_none_message_stats(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test sort handles None message_stats (treat as 0)."""
        # Arrange
        request = ListExchangesRequest(vhost="/", sort_by="message_in_rate")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "name": "no_stats",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            },
            {
                "name": "with_stats",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
                "message_stats": {"publish_in": 10.0, "publish_out": 9.5},
            },
        ]
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        names = [ex.name for ex in result]
        assert names == ["with_stats", "no_stats"]  # with_stats first (10.0 > 0)

    @pytest.mark.asyncio
    async def test_sort_after_filtering(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test sorting applied after filtering."""
        # Arrange
        request = ListExchangesRequest(vhost="/", type="direct", sort_by="name")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "name": "zebra",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            },
            {
                "name": "alpha",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            },
            {
                "name": "beta",
                "vhost": "/",
                "type": "topic",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            },
        ]
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        names = [ex.name for ex in result]
        assert names == ["alpha", "zebra"]  # Filtered type="direct", sorted by name

    @pytest.mark.asyncio
    async def test_case_insensitive_name_sort(
        self, list_exchanges_operation, mock_http_client
    ):
        """Test name sorting is case-insensitive."""
        # Arrange
        request = ListExchangesRequest(vhost="/", sort_by="name")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "name": "Zebra",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            },
            {
                "name": "alpha",
                "vhost": "/",
                "type": "direct",
                "durable": True,
                "auto_delete": False,
                "internal": False,
                "arguments": {},
            },
        ]
        mock_http_client.request.return_value = mock_response

        # Act
        result = await list_exchanges_operation.execute(request)

        # Assert
        names = [ex.name for ex in result]
        assert names == ["alpha", "Zebra"]


# =====================================================================
# Performance Monitoring Tests (~2 scenarios)
# =====================================================================


class TestPerformanceMonitoring:
    """Test performance monitoring and logging (AC #4, Task 8)."""

    @pytest.mark.asyncio
    async def test_logs_warning_when_slow(
        self, list_exchanges_operation, mock_http_client, caplog, monkeypatch
    ):
        """Test warning logged when operation exceeds 200ms threshold."""
        # Arrange
        request = ListExchangesRequest(vhost="/")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = []
        mock_http_client.request.return_value = mock_response

        # Mock time.time() to simulate slow operation
        import time

        call_count = [0]

        def mock_time():
            call_count[0] += 1
            if call_count[0] == 1:
                return 0.0  # Start time
            else:
                return 0.25  # End time (250ms later)

        monkeypatch.setattr(time, "time", mock_time)

        # Act
        await list_exchanges_operation.execute(request)

        # Assert
        # Performance warning should be logged
        # In actual implementation, check logs for "exchange.list.slow"

    @pytest.mark.asyncio
    async def test_no_warning_when_fast(
        self, list_exchanges_operation, mock_http_client, caplog, monkeypatch
    ):
        """Test no warning when operation is under 200ms threshold."""
        # Arrange
        request = ListExchangesRequest(vhost="/")
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = []
        mock_http_client.request.return_value = mock_response

        # Mock time.time() to simulate fast operation
        import time

        call_count = [0]

        def mock_time():
            call_count[0] += 1
            if call_count[0] == 1:
                return 0.0  # Start time
            else:
                return 0.15  # End time (150ms later)

        monkeypatch.setattr(time, "time", mock_time)

        # Act
        await list_exchanges_operation.execute(request)

        # Assert
        # No performance warning should be logged
