"""Unit tests for ListBindingsOperation (Story 3.8, Tasks 3-7)."""

from __future__ import annotations

from unittest.mock import AsyncMock, MagicMock

import httpx
import pytest

from rabbitmq_mcp_connection.operations.topology import ListBindingsOperation
from rabbitmq_mcp_connection.schemas.topology import BindingInfo, ListBindingsRequest


class TestListBindingsOperation:
    """Test ListBindingsOperation class (Story 3.8, AC #1-7)."""

    @pytest.fixture
    def mock_http_client(self) -> MagicMock:
        """Create mock HTTPClientManager."""
        return MagicMock()

    @pytest.fixture
    def operation(self, mock_http_client: MagicMock) -> ListBindingsOperation:
        """Create ListBindingsOperation instance."""
        return ListBindingsOperation(mock_http_client)

    @pytest.mark.asyncio
    async def test_list_all_bindings_success(
        self, operation: ListBindingsOperation, mock_http_client: MagicMock
    ):
        """Test listing all bindings in vhost (AC #1)."""
        # Mock successful API response
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "source": "events",
                "destination": "orders-queue",
                "destination_type": "queue",
                "routing_key": "order.created",
                "vhost": "/",
                "properties_key": "~order.created",
            },
            {
                "source": "events",
                "destination": "payments-queue",
                "destination_type": "queue",
                "routing_key": "payment.processed",
                "vhost": "/",
            },
        ]
        mock_http_client.request = AsyncMock(return_value=mock_response)

        request = ListBindingsRequest(vhost="/")
        result = await operation.execute(request)

        # Verify results
        assert len(result) == 2
        assert all(isinstance(b, BindingInfo) for b in result)
        assert result[0].source == "events"
        assert result[0].destination == "orders-queue"
        assert result[0].destination_type == "queue"
        assert result[0].routing_key == "order.created"
        assert result[1].destination == "payments-queue"

        # Verify API call
        mock_http_client.request.assert_called_once_with(
            method="GET",
            path="/api/bindings/%2F",
        )

    @pytest.mark.asyncio
    async def test_list_bindings_empty_vhost(
        self, operation: ListBindingsOperation, mock_http_client: MagicMock
    ):
        """Test listing bindings in empty vhost returns empty list (AC #6)."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = []
        mock_http_client.request = AsyncMock(return_value=mock_response)

        request = ListBindingsRequest(vhost="/")
        result = await operation.execute(request)

        assert result == []
        assert isinstance(result, list)

    @pytest.mark.asyncio
    async def test_list_bindings_filter_by_source(
        self, operation: ListBindingsOperation, mock_http_client: MagicMock
    ):
        """Test filtering bindings by source exchange (AC #2, Task 4)."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "source": "events",
                "destination": "orders-queue",
                "destination_type": "queue",
                "routing_key": "order.created",
                "vhost": "/",
            },
            {
                "source": "logs",
                "destination": "error-queue",
                "destination_type": "queue",
                "routing_key": "error.*",
                "vhost": "/",
            },
            {
                "source": "events",
                "destination": "payments-queue",
                "destination_type": "queue",
                "routing_key": "payment.processed",
                "vhost": "/",
            },
        ]
        mock_http_client.request = AsyncMock(return_value=mock_response)

        request = ListBindingsRequest(vhost="/", source="events")
        result = await operation.execute(request)

        assert len(result) == 2
        assert all(b.source == "events" for b in result)
        assert result[0].destination == "orders-queue"
        assert result[1].destination == "payments-queue"

    @pytest.mark.asyncio
    async def test_list_bindings_filter_by_destination(
        self, operation: ListBindingsOperation, mock_http_client: MagicMock
    ):
        """Test filtering bindings by destination (AC #2, Task 4)."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "source": "events",
                "destination": "orders-queue",
                "destination_type": "queue",
                "routing_key": "order.*",
                "vhost": "/",
            },
            {
                "source": "logs",
                "destination": "error-queue",
                "destination_type": "queue",
                "routing_key": "error.*",
                "vhost": "/",
            },
        ]
        mock_http_client.request = AsyncMock(return_value=mock_response)

        request = ListBindingsRequest(vhost="/", destination="orders-queue")
        result = await operation.execute(request)

        assert len(result) == 1
        assert result[0].destination == "orders-queue"
        assert result[0].source == "events"

    @pytest.mark.asyncio
    async def test_list_bindings_filter_by_destination_type(
        self, operation: ListBindingsOperation, mock_http_client: MagicMock
    ):
        """Test filtering bindings by destination_type (AC #2, #4, Task 4)."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "source": "upstream",
                "destination": "orders-queue",
                "destination_type": "queue",
                "routing_key": "order.*",
                "vhost": "/",
            },
            {
                "source": "upstream",
                "destination": "downstream",
                "destination_type": "exchange",
                "routing_key": "events.*",
                "vhost": "/",
            },
        ]
        mock_http_client.request = AsyncMock(return_value=mock_response)

        request = ListBindingsRequest(vhost="/", destination_type="exchange")
        result = await operation.execute(request)

        assert len(result) == 1
        assert result[0].destination_type == "exchange"
        assert result[0].destination == "downstream"

    @pytest.mark.asyncio
    async def test_list_bindings_combined_filters(
        self, operation: ListBindingsOperation, mock_http_client: MagicMock
    ):
        """Test combining multiple filters (AC #2, Task 4)."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "source": "events",
                "destination": "orders-queue",
                "destination_type": "queue",
                "routing_key": "order.created",
                "vhost": "/",
            },
            {
                "source": "events",
                "destination": "downstream-exchange",
                "destination_type": "exchange",
                "routing_key": "events.*",
                "vhost": "/",
            },
            {
                "source": "logs",
                "destination": "error-queue",
                "destination_type": "queue",
                "routing_key": "error.*",
                "vhost": "/",
            },
        ]
        mock_http_client.request = AsyncMock(return_value=mock_response)

        request = ListBindingsRequest(
            vhost="/", source="events", destination_type="queue"
        )
        result = await operation.execute(request)

        assert len(result) == 1
        assert result[0].source == "events"
        assert result[0].destination_type == "queue"
        assert result[0].destination == "orders-queue"

    @pytest.mark.asyncio
    async def test_list_bindings_sort_by_source(
        self, operation: ListBindingsOperation, mock_http_client: MagicMock
    ):
        """Test sorting bindings by source (AC #7, Task 4)."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "source": "zebra",
                "destination": "queue1",
                "destination_type": "queue",
                "routing_key": "key1",
                "vhost": "/",
            },
            {
                "source": "apple",
                "destination": "queue2",
                "destination_type": "queue",
                "routing_key": "key2",
                "vhost": "/",
            },
            {
                "source": "banana",
                "destination": "queue3",
                "destination_type": "queue",
                "routing_key": "key3",
                "vhost": "/",
            },
        ]
        mock_http_client.request = AsyncMock(return_value=mock_response)

        request = ListBindingsRequest(vhost="/", sort_by="source")
        result = await operation.execute(request)

        assert len(result) == 3
        assert result[0].source == "apple"
        assert result[1].source == "banana"
        assert result[2].source == "zebra"

    @pytest.mark.asyncio
    async def test_list_bindings_sort_by_destination(
        self, operation: ListBindingsOperation, mock_http_client: MagicMock
    ):
        """Test sorting bindings by destination (AC #7, Task 4)."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "source": "events",
                "destination": "zebra-queue",
                "destination_type": "queue",
                "routing_key": "key1",
                "vhost": "/",
            },
            {
                "source": "events",
                "destination": "apple-queue",
                "destination_type": "queue",
                "routing_key": "key2",
                "vhost": "/",
            },
        ]
        mock_http_client.request = AsyncMock(return_value=mock_response)

        request = ListBindingsRequest(vhost="/", sort_by="destination")
        result = await operation.execute(request)

        assert result[0].destination == "apple-queue"
        assert result[1].destination == "zebra-queue"

    @pytest.mark.asyncio
    async def test_list_bindings_sort_by_routing_key(
        self, operation: ListBindingsOperation, mock_http_client: MagicMock
    ):
        """Test sorting bindings by routing_key (AC #7, Task 4)."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "source": "events",
                "destination": "queue1",
                "destination_type": "queue",
                "routing_key": "zebra.key",
                "vhost": "/",
            },
            {
                "source": "events",
                "destination": "queue2",
                "destination_type": "queue",
                "routing_key": "apple.key",
                "vhost": "/",
            },
        ]
        mock_http_client.request = AsyncMock(return_value=mock_response)

        request = ListBindingsRequest(vhost="/", sort_by="routing_key")
        result = await operation.execute(request)

        assert result[0].routing_key == "apple.key"
        assert result[1].routing_key == "zebra.key"

    @pytest.mark.asyncio
    async def test_list_bindings_vhost_not_found(
        self, operation: ListBindingsOperation, mock_http_client: MagicMock
    ):
        """Test 404 error when vhost doesn't exist (AC #1)."""
        mock_response = MagicMock()
        mock_response.status_code = 404
        mock_http_client.request = AsyncMock(return_value=mock_response)

        request = ListBindingsRequest(vhost="/nonexistent")

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

        assert "does not exist" in str(exc_info.value)

    @pytest.mark.asyncio
    async def test_list_bindings_timeout(
        self, operation: ListBindingsOperation, mock_http_client: MagicMock
    ):
        """Test timeout handling (AC #5)."""
        mock_http_client.request = AsyncMock(
            side_effect=httpx.TimeoutException("timeout")
        )

        request = ListBindingsRequest(vhost="/")

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

    @pytest.mark.asyncio
    async def test_list_bindings_with_correlation_id(
        self, operation: ListBindingsOperation, mock_http_client: MagicMock
    ):
        """Test operation with correlation_id for audit trail (Task 6)."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = []
        mock_http_client.request = AsyncMock(return_value=mock_response)

        request = ListBindingsRequest(vhost="/")
        await operation.execute(request, correlation_id="test-correlation-123")

        # Verify operation completed successfully (correlation_id logged)
        mock_http_client.request.assert_called_once()

    @pytest.mark.asyncio
    async def test_list_bindings_exchange_to_exchange(
        self, operation: ListBindingsOperation, mock_http_client: MagicMock
    ):
        """Test listing exchange-to-exchange bindings (AC #4, Task 5)."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "source": "upstream-exchange",
                "destination": "downstream-exchange",
                "destination_type": "exchange",
                "routing_key": "events.*",
                "vhost": "/",
                "properties_key": "~events.*",
            }
        ]
        mock_http_client.request = AsyncMock(return_value=mock_response)

        request = ListBindingsRequest(vhost="/")
        result = await operation.execute(request)

        assert len(result) == 1
        assert result[0].destination_type == "exchange"
        assert result[0].source == "upstream-exchange"
        assert result[0].destination == "downstream-exchange"

    @pytest.mark.asyncio
    async def test_list_bindings_fanout_empty_routing_key(
        self, operation: ListBindingsOperation, mock_http_client: MagicMock
    ):
        """Test fanout binding with empty routing_key (Task 5)."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "source": "notifications",
                "destination": "email-queue",
                "destination_type": "queue",
                "routing_key": "",
                "vhost": "/",
            }
        ]
        mock_http_client.request = AsyncMock(return_value=mock_response)

        request = ListBindingsRequest(vhost="/")
        result = await operation.execute(request)

        assert len(result) == 1
        assert result[0].routing_key == ""

    @pytest.mark.asyncio
    async def test_list_bindings_with_arguments(
        self, operation: ListBindingsOperation, mock_http_client: MagicMock
    ):
        """Test binding with arguments (headers exchange, Task 5)."""
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {
                "source": "headers-exchange",
                "destination": "priority-queue",
                "destination_type": "queue",
                "routing_key": "",
                "arguments": {"x-match": "all", "priority": "high"},
                "vhost": "/",
            }
        ]
        mock_http_client.request = AsyncMock(return_value=mock_response)

        request = ListBindingsRequest(vhost="/")
        result = await operation.execute(request)

        assert len(result) == 1
        assert result[0].arguments == {"x-match": "all", "priority": "high"}
