"""Unit tests for CreateQueueOperation (Story 3.2 Task 2, 3)."""

from unittest.mock import AsyncMock, Mock

import httpx
import pytest

from rabbitmq_mcp_connection.operations.topology import CreateQueueOperation
from rabbitmq_mcp_connection.schemas.topology import CreateQueueRequest, 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 CreateQueueOperation instance."""
    return CreateQueueOperation(mock_http_client)


@pytest.fixture
def sample_queue_response():
    """Sample queue response from Management API."""
    return {
        "name": "test-queue",
        "vhost": "/",
        "durable": True,
        "auto_delete": False,
        "arguments": {},
        "messages": 0,
        "messages_ready": 0,
        "messages_unacknowledged": 0,
        "consumers": 0,
        "memory": 0,
        "state": "idle",
    }


class TestCreateQueueOperation:
    """Test CreateQueueOperation.execute() method (AC #1-8)."""

    @pytest.mark.asyncio
    async def test_execute_create_new_queue(
        self, operation, mock_http_client, sample_queue_response
    ):
        """Test successful queue creation (AC #1, #2)."""
        # Setup mock responses
        # First GET returns 404 (queue doesn't exist)
        get_response = Mock()
        get_response.status_code = 404

        # PUT returns 201 Created
        put_response = Mock()
        put_response.status_code = 201
        put_response.json.return_value = {}

        # Second GET returns queue details
        get_after_create = Mock()
        get_after_create.status_code = 200
        get_after_create.json.return_value = sample_queue_response

        mock_http_client.request.side_effect = [
            get_response,
            put_response,
            get_after_create,
        ]

        # Execute
        request = CreateQueueRequest(name="test-queue", vhost="/", durable=True)
        result = await operation.execute(request)

        # Verify result
        assert result["status"] == "success"
        assert "queue_details" in result
        assert result["queue_details"]["name"] == "test-queue"

        # Verify HTTP calls
        assert mock_http_client.request.call_count == 3
        # First call: GET to check existence
        assert mock_http_client.request.call_args_list[0][0] == (
            "GET",
            "/queues/%2F/test-queue",
        )
        # Second call: PUT to create
        assert mock_http_client.request.call_args_list[1][0] == (
            "PUT",
            "/queues/%2F/test-queue",
        )
        assert mock_http_client.request.call_args_list[1][1]["json"] == {
            "durable": True,
            "auto_delete": False,
        }

    @pytest.mark.asyncio
    async def test_execute_with_arguments(
        self, operation, mock_http_client, sample_queue_response
    ):
        """Test queue creation with arguments (AC #2)."""
        # Setup mock responses
        get_response = Mock()
        get_response.status_code = 404

        put_response = Mock()
        put_response.status_code = 201

        queue_with_args = sample_queue_response.copy()
        queue_with_args["arguments"] = {
            "x-message-ttl": 60000,
            "x-max-length": 1000,
        }

        get_after_create = Mock()
        get_after_create.status_code = 200
        get_after_create.json.return_value = queue_with_args

        mock_http_client.request.side_effect = [
            get_response,
            put_response,
            get_after_create,
        ]

        # Execute with arguments
        request = CreateQueueRequest(
            name="ttl-queue",
            arguments={"x-message-ttl": 60000, "x-max-length": 1000},
        )
        result = await operation.execute(request)

        # Verify arguments in PUT body
        put_call = mock_http_client.request.call_args_list[1]
        assert put_call[1]["json"]["arguments"] == {
            "x-message-ttl": 60000,
            "x-max-length": 1000,
        }

        assert result["status"] == "success"

    @pytest.mark.asyncio
    async def test_execute_idempotent_same_properties(
        self, operation, mock_http_client, sample_queue_response
    ):
        """Test idempotent creation with matching properties (AC #4)."""
        # Queue already exists with same properties
        get_response = Mock()
        get_response.status_code = 200
        get_response.json.return_value = sample_queue_response
        get_response.raise_for_status = Mock()

        mock_http_client.request.return_value = get_response

        # Execute
        request = CreateQueueRequest(name="test-queue", vhost="/", durable=True)
        result = await operation.execute(request)

        # Verify result - should succeed without PUT
        assert result["status"] == "success"
        assert result["queue_details"]["name"] == "test-queue"

        # Verify only GET was called (no PUT)
        assert mock_http_client.request.call_count == 1
        assert mock_http_client.request.call_args[0] == (
            "GET",
            "/queues/%2F/test-queue",
        )

    @pytest.mark.asyncio
    async def test_execute_conflict_different_properties(
        self, operation, mock_http_client, sample_queue_response
    ):
        """Test conflict when queue exists with different properties (AC #5)."""
        # Queue exists with different durable setting
        existing_queue = sample_queue_response.copy()
        existing_queue["durable"] = False

        get_response = Mock()
        get_response.status_code = 200
        get_response.json.return_value = existing_queue
        get_response.raise_for_status = Mock()

        mock_http_client.request.return_value = get_response

        # Execute with different properties
        request = CreateQueueRequest(name="test-queue", vhost="/", durable=True)

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

        error_msg = str(exc_info.value)
        assert "already exists with different configuration" in error_msg
        assert "test-queue" in error_msg

    @pytest.mark.asyncio
    async def test_execute_conflict_different_arguments(
        self, operation, mock_http_client, sample_queue_response
    ):
        """Test conflict when queue exists with different arguments (AC #5)."""
        # Queue exists with different arguments
        existing_queue = sample_queue_response.copy()
        existing_queue["arguments"] = {"x-message-ttl": 30000}

        get_response = Mock()
        get_response.status_code = 200
        get_response.json.return_value = existing_queue
        get_response.raise_for_status = Mock()

        mock_http_client.request.return_value = get_response

        # Execute with different arguments
        request = CreateQueueRequest(
            name="test-queue",
            arguments={"x-message-ttl": 60000},
        )

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

        error_msg = str(exc_info.value)
        assert "already exists with different configuration" in error_msg

    @pytest.mark.asyncio
    async def test_execute_race_condition_204_response(
        self, operation, mock_http_client, sample_queue_response
    ):
        """Test race condition handling (204 No Content) (AC #1, Task 3)."""
        # GET returns 404 (queue doesn't exist)
        get_response = Mock()
        get_response.status_code = 404

        # PUT returns 204 (queue created between GET and PUT)
        put_response = Mock()
        put_response.status_code = 204

        # Second GET returns queue details
        get_after_create = Mock()
        get_after_create.status_code = 200
        get_after_create.json.return_value = sample_queue_response

        mock_http_client.request.side_effect = [
            get_response,
            put_response,
            get_after_create,
        ]

        # Execute
        request = CreateQueueRequest(name="test-queue")
        result = await operation.execute(request)

        # Should handle 204 as success
        assert result["status"] == "success"

    @pytest.mark.asyncio
    async def test_execute_timeout(self, operation, mock_http_client):
        """Test timeout handling (AC #8)."""
        # GET times out
        mock_http_client.request.side_effect = httpx.TimeoutException("Timeout")

        # Execute
        request = CreateQueueRequest(name="test-queue")

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

    @pytest.mark.asyncio
    async def test_execute_http_error(self, operation, mock_http_client):
        """Test HTTP error handling."""
        # GET returns 404, PUT returns 400 Bad Request
        get_response = Mock()
        get_response.status_code = 404

        put_response = Mock()
        put_response.status_code = 400
        put_response.raise_for_status = Mock(
            side_effect=httpx.HTTPStatusError(
                "Bad Request", request=Mock(), response=put_response
            )
        )

        mock_http_client.request.side_effect = [get_response, put_response]

        # Execute
        request = CreateQueueRequest(name="test-queue")

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

    @pytest.mark.asyncio
    async def test_execute_verification_failure(self, operation, mock_http_client):
        """Test queue creation verification failure."""
        # GET returns 404 (queue doesn't exist)
        get_response = Mock()
        get_response.status_code = 404

        # PUT returns 201 Created
        put_response = Mock()
        put_response.status_code = 201

        # GET after create returns 404 (verification fails)
        get_verification = Mock()
        get_verification.status_code = 404

        mock_http_client.request.side_effect = [
            get_response,
            put_response,
            get_verification,
        ]

        # Execute
        request = CreateQueueRequest(name="test-queue")

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

        assert "verification failed" in str(exc_info.value).lower()

    @pytest.mark.asyncio
    async def test_url_encoding_vhost(self, operation, mock_http_client):
        """Test URL encoding of vhost parameter."""
        # Setup mock
        get_response = Mock()
        get_response.status_code = 404

        put_response = Mock()
        put_response.status_code = 201

        get_after = Mock()
        get_after.status_code = 200
        get_after.json.return_value = {
            "name": "test-queue",
            "vhost": "/prod",
            "durable": True,
            "auto_delete": False,
            "arguments": {},
            "messages": 0,
            "messages_ready": 0,
            "messages_unacknowledged": 0,
            "consumers": 0,
            "memory": 0,
            "state": "idle",
        }

        mock_http_client.request.side_effect = [
            get_response,
            put_response,
            get_after,
        ]

        # Execute with vhost="/prod"
        request = CreateQueueRequest(name="test-queue", vhost="/prod")
        await operation.execute(request)

        # Verify vhost was URL encoded
        assert mock_http_client.request.call_args_list[0][0] == (
            "GET",
            "/queues/%2Fprod/test-queue",
        )

    @pytest.mark.asyncio
    async def test_url_encoding_queue_name(self, operation, mock_http_client):
        """Test URL encoding of queue name with special characters."""
        get_response = Mock()
        get_response.status_code = 404

        put_response = Mock()
        put_response.status_code = 201

        get_after = Mock()
        get_after.status_code = 200
        get_after.json.return_value = {
            "name": "test.queue",
            "vhost": "/",
            "durable": True,
            "auto_delete": False,
            "arguments": {},
            "messages": 0,
            "messages_ready": 0,
            "messages_unacknowledged": 0,
            "consumers": 0,
            "memory": 0,
            "state": "idle",
        }

        mock_http_client.request.side_effect = [
            get_response,
            put_response,
            get_after,
        ]

        # Execute with queue name containing period
        request = CreateQueueRequest(name="test.queue")
        await operation.execute(request)

        # Verify queue name in URL (period doesn't need encoding)
        assert "test.queue" in mock_http_client.request.call_args_list[0][0][1]

    @pytest.mark.asyncio
    async def test_correlation_id_logging(self, operation, mock_http_client):
        """Test correlation_id is passed through for logging (ADR-009)."""
        get_response = Mock()
        get_response.status_code = 404

        put_response = Mock()
        put_response.status_code = 201

        get_after = Mock()
        get_after.status_code = 200
        get_after.json.return_value = {
            "name": "test-queue",
            "vhost": "/",
            "durable": True,
            "auto_delete": False,
            "arguments": {},
            "messages": 0,
            "messages_ready": 0,
            "messages_unacknowledged": 0,
            "consumers": 0,
            "memory": 0,
            "state": "idle",
        }

        mock_http_client.request.side_effect = [
            get_response,
            put_response,
            get_after,
        ]

        # Execute with correlation_id
        request = CreateQueueRequest(name="test-queue")
        await operation.execute(request, correlation_id="test-correlation-123")

        # Note: Actual correlation_id logging verification would require mocking logger
        # This test ensures correlation_id parameter is accepted


class TestPropertiesMatch:
    """Test _properties_match helper method (Task 3)."""

    def test_properties_match_exact(self, operation):
        """Test properties match exactly."""
        request = CreateQueueRequest(
            name="test-queue",
            durable=True,
            auto_delete=False,
            arguments={"x-message-ttl": 60000},
        )

        existing = QueueDetails(
            name="test-queue",
            vhost="/",
            durable=True,
            auto_delete=False,
            arguments={"x-message-ttl": 60000},
        )

        assert operation._properties_match(request, existing) is True

    def test_properties_match_durable_differs(self, operation):
        """Test properties don't match when durable differs."""
        request = CreateQueueRequest(name="test-queue", durable=True)

        existing = QueueDetails(
            name="test-queue",
            vhost="/",
            durable=False,
            auto_delete=False,
        )

        assert operation._properties_match(request, existing) is False

    def test_properties_match_auto_delete_differs(self, operation):
        """Test properties don't match when auto_delete differs."""
        request = CreateQueueRequest(name="test-queue", auto_delete=True)

        existing = QueueDetails(
            name="test-queue",
            vhost="/",
            durable=True,
            auto_delete=False,
        )

        assert operation._properties_match(request, existing) is False

    def test_properties_match_arguments_differ(self, operation):
        """Test properties don't match when arguments differ."""
        request = CreateQueueRequest(
            name="test-queue",
            arguments={"x-message-ttl": 60000},
        )

        existing = QueueDetails(
            name="test-queue",
            vhost="/",
            durable=True,
            auto_delete=False,
            arguments={"x-message-ttl": 30000},
        )

        assert operation._properties_match(request, existing) is False

    def test_properties_match_none_vs_empty_arguments(self, operation):
        """Test None arguments treated as empty dict."""
        request = CreateQueueRequest(name="test-queue", arguments=None)

        existing = QueueDetails(
            name="test-queue",
            vhost="/",
            durable=True,
            auto_delete=False,
            arguments={},
        )

        assert operation._properties_match(request, existing) is True

    def test_properties_match_empty_vs_none_arguments(self, operation):
        """Test empty dict arguments match None."""
        request = CreateQueueRequest(name="test-queue", arguments={})

        existing = QueueDetails(
            name="test-queue",
            vhost="/",
            durable=True,
            auto_delete=False,
            arguments={},
        )

        assert operation._properties_match(request, existing) is True
