"""Unit tests for ListQueuesOperation (Story 3.1 Task 2)."""

from unittest.mock import AsyncMock, Mock, patch

import httpx
import pytest

from rabbitmq_mcp_connection.operations.topology import ListQueuesOperation
from rabbitmq_mcp_connection.schemas.topology import QueueDetails


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


@pytest.fixture
def operation(mock_http_client):
    """Create ListQueuesOperation instance."""
    return ListQueuesOperation(mock_http_client)


@pytest.fixture
def sample_queue_data():
    """Sample queue data from Management API."""
    return [
        {
            "name": "orders-queue",
            "vhost": "/",
            "durable": True,
            "auto_delete": False,
            "arguments": {"x-max-length": 1000},
            "messages": 100,
            "messages_ready": 50,
            "messages_unacknowledged": 50,
            "consumers": 2,
            "memory": 1048576,
            "state": "running",
        },
        {
            "name": "events-queue",
            "vhost": "/",
            "durable": True,
            "auto_delete": False,
            "arguments": {},
            "messages": 0,
            "messages_ready": 0,
            "messages_unacknowledged": 0,
            "consumers": 0,
            "memory": 512000,
            "state": "idle",
        },
        {
            "name": "notifications-queue",
            "vhost": "/",
            "durable": False,
            "auto_delete": True,
            "arguments": {},
            "messages": 500,
            "messages_ready": 500,
            "messages_unacknowledged": 0,
            "consumers": 5,
            "memory": 2097152,
            "state": "flow",
        },
    ]


class TestListQueuesOperation:
    """Test ListQueuesOperation.execute() method (AC #1-6)."""

    @pytest.mark.asyncio
    async def test_execute_success(
        self, operation, mock_http_client, sample_queue_data
    ):
        """Test successful queue list execution (AC #1, #2, #3)."""
        # Setup mock response
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = sample_queue_data
        mock_http_client.request.return_value = mock_response

        # Execute
        queues = await operation.execute(vhost="/")

        # Verify HTTP call
        mock_http_client.request.assert_called_once_with("GET", "/queues/%2F")

        # Verify results
        assert len(queues) == 3
        assert all(isinstance(q, QueueDetails) for q in queues)

        # Verify first queue
        assert queues[0].name == "orders-queue"
        assert queues[0].vhost == "/"
        assert queues[0].durable is True
        assert queues[0].auto_delete is False
        assert queues[0].messages == 100
        assert queues[0].consumers == 2
        assert queues[0].state == "running"

    @pytest.mark.asyncio
    async def test_execute_empty_vhost(self, operation, mock_http_client):
        """Test empty vhost returns empty list (AC #5)."""
        # Setup mock response
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = []
        mock_http_client.request.return_value = mock_response

        # Execute
        queues = await operation.execute(vhost="/test")

        # Verify
        assert queues == []
        assert isinstance(queues, list)

    @pytest.mark.asyncio
    async def test_execute_vhost_not_found(self, operation, mock_http_client):
        """Test non-existent vhost returns error (AC #6)."""
        # Setup mock response
        mock_response = Mock()
        mock_response.status_code = 404
        mock_http_client.request.return_value = mock_response

        # Execute and verify error
        with pytest.raises(
            ValueError, match="Virtual host '/nonexistent' does not exist"
        ):
            await operation.execute(vhost="/nonexistent")

    @pytest.mark.asyncio
    async def test_execute_with_filtering(
        self, operation, mock_http_client, sample_queue_data
    ):
        """Test filtering by name pattern (AC #7)."""
        # Setup mock response
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = sample_queue_data
        mock_http_client.request.return_value = mock_response

        # Execute with name filter
        queues = await operation.execute(vhost="/", name_pattern="orders-.*")

        # Verify only matching queues returned
        assert len(queues) == 1
        assert queues[0].name == "orders-queue"

    @pytest.mark.asyncio
    async def test_execute_with_state_filter(
        self, operation, mock_http_client, sample_queue_data
    ):
        """Test filtering by state (AC #7)."""
        # Setup mock response
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = sample_queue_data
        mock_http_client.request.return_value = mock_response

        # Execute with state filter
        queues = await operation.execute(vhost="/", state="idle")

        # Verify only idle queues returned
        assert len(queues) == 1
        assert queues[0].name == "events-queue"
        assert queues[0].state == "idle"

    @pytest.mark.asyncio
    async def test_execute_with_consumer_filter(
        self, operation, mock_http_client, sample_queue_data
    ):
        """Test filtering by consumer count (AC #7)."""
        # Setup mock response
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = sample_queue_data
        mock_http_client.request.return_value = mock_response

        # Execute with consumer filter
        queues = await operation.execute(vhost="/", consumers=0)

        # Verify only queues with 0 consumers returned
        assert len(queues) == 1
        assert queues[0].name == "events-queue"
        assert queues[0].consumers == 0

    @pytest.mark.asyncio
    async def test_execute_with_sorting(
        self, operation, mock_http_client, sample_queue_data
    ):
        """Test sorting by messages (AC #8)."""
        # Setup mock response
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = sample_queue_data
        mock_http_client.request.return_value = mock_response

        # Execute with sort
        queues = await operation.execute(vhost="/", sort_by="messages", reverse=True)

        # Verify sorted descending by messages
        assert len(queues) == 3
        assert queues[0].name == "notifications-queue"  # 500 messages
        assert queues[1].name == "orders-queue"  # 100 messages
        assert queues[2].name == "events-queue"  # 0 messages

    @pytest.mark.asyncio
    async def test_execute_with_pagination(
        self, operation, mock_http_client, sample_queue_data
    ):
        """Test pagination (AC #4)."""
        # Setup mock response with more data
        extended_data = sample_queue_data * 10  # 30 queues
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = extended_data
        mock_http_client.request.return_value = mock_response

        # Execute page 0 with page_size=10
        queues = await operation.execute(vhost="/", page=0, page_size=10)

        # Verify pagination
        assert len(queues) == 10

        # Execute page 1
        queues_page1 = await operation.execute(vhost="/", page=1, page_size=10)
        assert len(queues_page1) == 10

    @pytest.mark.asyncio
    async def test_execute_timeout(self, operation, mock_http_client):
        """Test timeout handling (AC #4)."""
        # Setup mock to raise timeout
        mock_http_client.request.side_effect = httpx.TimeoutException("Request timeout")

        # Execute and verify timeout error
        with pytest.raises(httpx.TimeoutException):
            await operation.execute(vhost="/")

    @pytest.mark.asyncio
    async def test_vhost_url_encoding(self, operation, mock_http_client):
        """Test vhost URL encoding (Architecture Constraints)."""
        # Setup mock response
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = []
        mock_http_client.request.return_value = mock_response

        # Execute with "/" vhost
        await operation.execute(vhost="/")

        # Verify URL encoding: "/" -> "%2F"
        mock_http_client.request.assert_called_once_with("GET", "/queues/%2F")


class TestFilterByNamePattern:
    """Test filter_by_name_pattern method (AC #7)."""

    def test_filter_by_name_pattern_match(self, operation):
        """Test name pattern filtering with matches."""
        queues = [
            QueueDetails(
                name="orders-prod", vhost="/", durable=True, auto_delete=False
            ),
            QueueDetails(name="orders-dev", vhost="/", durable=True, auto_delete=False),
            QueueDetails(
                name="events-prod", vhost="/", durable=True, auto_delete=False
            ),
        ]

        result = operation.filter_by_name_pattern(queues, "orders-.*")

        assert len(result) == 2
        assert result[0].name == "orders-prod"
        assert result[1].name == "orders-dev"

    def test_filter_by_name_pattern_no_match(self, operation):
        """Test name pattern filtering with no matches."""
        queues = [
            QueueDetails(name="queue1", vhost="/", durable=True, auto_delete=False),
            QueueDetails(name="queue2", vhost="/", durable=True, auto_delete=False),
        ]

        result = operation.filter_by_name_pattern(queues, "nomatch-.*")

        assert result == []

    def test_filter_by_name_pattern_invalid_regex(self, operation):
        """Test invalid regex pattern raises ValueError (AC #7)."""
        queues = [
            QueueDetails(name="queue1", vhost="/", durable=True, auto_delete=False),
        ]

        with pytest.raises(ValueError, match="Invalid regex pattern"):
            operation.filter_by_name_pattern(queues, "[invalid(")


class TestFilterByState:
    """Test filter_by_state method (AC #7)."""

    def test_filter_by_state(self, operation):
        """Test state filtering."""
        queues = [
            QueueDetails(
                name="q1", vhost="/", durable=True, auto_delete=False, state="running"
            ),
            QueueDetails(
                name="q2", vhost="/", durable=True, auto_delete=False, state="idle"
            ),
            QueueDetails(
                name="q3", vhost="/", durable=True, auto_delete=False, state="running"
            ),
        ]

        result = operation.filter_by_state(queues, "running")

        assert len(result) == 2
        assert all(q.state == "running" for q in result)


class TestFilterByConsumerCount:
    """Test filter_by_consumer_count method (AC #7, Task 3)."""

    def test_filter_by_consumer_count_equals(self, operation):
        """Test consumer count filtering with equals operator."""
        queues = [
            QueueDetails(
                name="q1", vhost="/", durable=True, auto_delete=False, consumers=0
            ),
            QueueDetails(
                name="q2", vhost="/", durable=True, auto_delete=False, consumers=5
            ),
            QueueDetails(
                name="q3", vhost="/", durable=True, auto_delete=False, consumers=0
            ),
        ]

        result = operation.filter_by_consumer_count(queues, 0, "=")

        assert len(result) == 2
        assert all(q.consumers == 0 for q in result)

    def test_filter_by_consumer_count_greater_than(self, operation):
        """Test consumer count filtering with greater than operator (Task 3)."""
        queues = [
            QueueDetails(
                name="q1", vhost="/", durable=True, auto_delete=False, consumers=0
            ),
            QueueDetails(
                name="q2", vhost="/", durable=True, auto_delete=False, consumers=5
            ),
            QueueDetails(
                name="q3", vhost="/", durable=True, auto_delete=False, consumers=3
            ),
        ]

        result = operation.filter_by_consumer_count(queues, 2, ">")

        assert len(result) == 2
        assert all(q.consumers > 2 for q in result)

    def test_filter_by_consumer_count_less_than(self, operation):
        """Test consumer count filtering with less than operator (Task 3)."""
        queues = [
            QueueDetails(
                name="q1", vhost="/", durable=True, auto_delete=False, consumers=0
            ),
            QueueDetails(
                name="q2", vhost="/", durable=True, auto_delete=False, consumers=5
            ),
            QueueDetails(
                name="q3", vhost="/", durable=True, auto_delete=False, consumers=3
            ),
        ]

        result = operation.filter_by_consumer_count(queues, 4, "<")

        assert len(result) == 2
        assert all(q.consumers < 4 for q in result)

    def test_filter_by_consumer_count_invalid_operator(self, operation):
        """Test invalid operator raises ValueError (Task 3)."""
        queues = [
            QueueDetails(
                name="q1", vhost="/", durable=True, auto_delete=False, consumers=0
            ),
        ]

        with pytest.raises(ValueError, match="Unsupported operator"):
            operation.filter_by_consumer_count(queues, 0, ">=")

    def test_filter_by_consumer_count_default_equals(self, operation):
        """Test default operator is equals (backward compatibility)."""
        queues = [
            QueueDetails(
                name="q1", vhost="/", durable=True, auto_delete=False, consumers=0
            ),
            QueueDetails(
                name="q2", vhost="/", durable=True, auto_delete=False, consumers=5
            ),
        ]

        result = operation.filter_by_consumer_count(queues, 0)

        assert len(result) == 1
        assert result[0].consumers == 0


class TestSortBy:
    """Test sort_by method (AC #8)."""

    @pytest.mark.parametrize(
        "field,expected_order",
        [
            ("name", ["q1", "q2", "q3"]),
            ("messages", ["q2", "q1", "q3"]),
            ("consumers", ["q2", "q1", "q3"]),
            ("memory", ["q2", "q1", "q3"]),
        ],
    )
    def test_sort_by_field(self, operation, field, expected_order):
        """Test sorting by different fields (AC #8)."""
        queues = [
            QueueDetails(
                name="q3",
                vhost="/",
                durable=True,
                auto_delete=False,
                messages=500,
                consumers=5,
                memory=3000,
            ),
            QueueDetails(
                name="q1",
                vhost="/",
                durable=True,
                auto_delete=False,
                messages=100,
                consumers=2,
                memory=1000,
            ),
            QueueDetails(
                name="q2",
                vhost="/",
                durable=True,
                auto_delete=False,
                messages=0,
                consumers=0,
                memory=500,
            ),
        ]

        result = operation.sort_by(queues, field)

        assert [q.name for q in result] == expected_order

    def test_sort_by_descending(self, operation):
        """Test descending sort order (AC #8)."""
        queues = [
            QueueDetails(
                name="q1", vhost="/", durable=True, auto_delete=False, messages=100
            ),
            QueueDetails(
                name="q2", vhost="/", durable=True, auto_delete=False, messages=200
            ),
            QueueDetails(
                name="q3", vhost="/", durable=True, auto_delete=False, messages=50
            ),
        ]

        result = operation.sort_by(queues, "messages", reverse=True)

        assert [q.messages for q in result] == [200, 100, 50]

    def test_sort_by_invalid_field(self, operation):
        """Test invalid sort field raises ValueError (AC #8)."""
        queues = [
            QueueDetails(name="q1", vhost="/", durable=True, auto_delete=False),
        ]

        with pytest.raises(ValueError, match="Unsupported sort field"):
            operation.sort_by(queues, "invalid_field")

    def test_sort_by_handles_zero_values(self, operation):
        """Test sorting handles zero values (AC #8)."""
        queues = [
            QueueDetails(
                name="q1", vhost="/", durable=True, auto_delete=False, memory=1000
            ),
            QueueDetails(
                name="q2", vhost="/", durable=True, auto_delete=False, memory=0
            ),
            QueueDetails(
                name="q3", vhost="/", durable=True, auto_delete=False, memory=500
            ),
        ]

        # Should not raise error
        result = operation.sort_by(queues, "memory")

        # Zero values should be first
        assert len(result) == 3
        assert result[0].name == "q2"
        assert result[0].memory == 0
