from __future__ import annotations

"""Unit tests for HTTPClient and HTTPClientManager classes."""

import base64
from unittest.mock import AsyncMock, MagicMock, patch

import httpx
import pytest

from rabbitmq_mcp_connection.http_client import (
    DEFAULT_POOL_SIZE,
    DEFAULT_TIMEOUT,
    HTTPClient,
    HTTPClientManager,
    PoolHealth,
    PoolMetrics,
)


class TestHTTPClientManager:
    """Test suite for HTTPClientManager class (Story 2.3)."""

    def test_init_default_parameters(self):
        """Test HTTPClientManager initialization with default parameters (AC #1, #5, #7)."""
        client = HTTPClientManager(
            host="localhost",
            port=15672,
            username="guest",
            password="guest",
        )

        assert client.base_url == "http://localhost:15672/api"
        assert client.username == "guest"
        assert client.password == "guest"
        assert client.use_tls is False
        assert client.verify_cert is True
        assert client.timeout == DEFAULT_TIMEOUT
        assert client.pool_size == DEFAULT_POOL_SIZE
        assert client.client is not None

    def test_init_with_tls(self):
        """Test HTTPClientManager with TLS enabled (AC #3, #4)."""
        client = HTTPClientManager(
            host="rabbitmq.example.com",
            port=15671,
            username="admin",
            password="secret",
            use_tls=True,
            verify_cert=False,
            timeout=60,
            pool_size=10,
        )

        assert client.base_url == "https://rabbitmq.example.com:15671/api"
        assert client.use_tls is True
        assert client.verify_cert is False
        assert client.timeout == 60
        assert client.pool_size == 10

    def test_build_auth_header(self):
        """Test HTTP Basic Auth header construction (AC #2)."""
        client = HTTPClientManager(
            host="localhost",
            port=15672,
            username="guest",
            password="guest",
        )

        auth_header = client._build_auth_header()

        # Verify format
        assert auth_header.startswith("Basic ")

        # Verify encoding
        expected = base64.b64encode(b"guest:guest").decode()
        assert auth_header == f"Basic {expected}"

    @pytest.mark.asyncio
    async def test_request_get(self):
        """Test GET request execution with correlation ID (AC #6)."""
        client = HTTPClientManager(
            host="localhost",
            port=15672,
            username="guest",
            password="guest",
        )

        # Mock httpx response
        mock_response = MagicMock(spec=httpx.Response)
        mock_response.status_code = 200
        mock_response.json.return_value = {"status": "ok"}

        with patch.object(
            client.client, "request", new_callable=AsyncMock
        ) as mock_request:
            mock_request.return_value = mock_response

            response = await client.request(
                method="GET",
                path="/overview",
            )

            assert response.status_code == 200
            assert response.json() == {"status": "ok"}

            # Verify request was made correctly
            mock_request.assert_called_once()
            call_kwargs = mock_request.call_args.kwargs
            assert call_kwargs["method"] == "GET"
            assert call_kwargs["url"] == "http://localhost:15672/api/overview"
            assert "Authorization" in call_kwargs["headers"]
            assert call_kwargs["headers"]["Authorization"].startswith("Basic ")
            assert "User-Agent" in call_kwargs["headers"]
            assert call_kwargs["headers"]["User-Agent"].startswith(
                "rabbitmq-mcp-server/"
            )
            assert "X-Correlation-ID" in call_kwargs["headers"]

    @pytest.mark.asyncio
    async def test_request_post_with_body(self):
        """Test POST request with JSON body."""
        client = HTTPClientManager(
            host="localhost",
            port=15672,
            username="guest",
            password="guest",
        )

        mock_response = MagicMock(spec=httpx.Response)
        mock_response.status_code = 201

        with patch.object(
            client.client, "request", new_callable=AsyncMock
        ) as mock_request:
            mock_request.return_value = mock_response

            response = await client.request(
                method="POST",
                path="/queues/%2F/test-queue",
                json={"durable": True, "auto_delete": False},
            )

            assert response.status_code == 201

            # Verify Content-Type header added
            call_kwargs = mock_request.call_args.kwargs
            assert call_kwargs["headers"]["Content-Type"] == "application/json"
            assert call_kwargs["json"] == {"durable": True, "auto_delete": False}

    @pytest.mark.asyncio
    async def test_request_timeout(self):
        """Test request timeout handling (AC #5, #8)."""
        client = HTTPClientManager(
            host="localhost",
            port=15672,
            username="guest",
            password="guest",
            timeout=1,
        )

        with patch.object(
            client.client, "request", new_callable=AsyncMock
        ) as mock_request:
            mock_request.side_effect = httpx.TimeoutException("Request timed out")

            with pytest.raises(httpx.TimeoutException):
                await client.request(method="GET", path="/overview")

            # Verify failure tracking
            assert client._failed_requests == 1

    @pytest.mark.asyncio
    async def test_request_connection_error(self):
        """Test connection error handling (AC #8)."""
        client = HTTPClientManager(
            host="localhost",
            port=15672,
            username="guest",
            password="guest",
        )

        with patch.object(
            client.client, "request", new_callable=AsyncMock
        ) as mock_request:
            mock_request.side_effect = httpx.ConnectError("Connection refused")

            with pytest.raises(httpx.ConnectError):
                await client.request(method="GET", path="/overview")

    @pytest.mark.asyncio
    async def test_close(self):
        """Test HTTP client graceful close."""
        client = HTTPClientManager(
            host="localhost",
            port=15672,
            username="guest",
            password="guest",
        )

        with patch.object(
            client.client, "aclose", new_callable=AsyncMock
        ) as mock_close:
            await client.close()
            mock_close.assert_called_once()

    def test_get_pool_status(self):
        """Test connection pool health metrics retrieval (Story 2.6 AC #7)."""
        client = HTTPClientManager(
            host="localhost",
            port=15672,
            username="guest",
            password="guest",
        )

        pool_status = client.get_pool_status()

        # Story 2.6: Now returns PoolMetrics instead of PoolHealth
        assert isinstance(pool_status, PoolMetrics)
        assert pool_status.pool_size == DEFAULT_POOL_SIZE
        assert pool_status.active_connections == 0
        assert pool_status.total_requests == 0
        assert pool_status.successful_requests == 0
        assert pool_status.failed_requests == 0
        assert pool_status.health_score == 1.0


class TestHTTPClient:
    """Test suite for HTTPClient class (backward compatibility)."""

    def test_init_default_parameters(self):
        """Test HTTPClient initialization with default parameters (legacy interface)."""
        client = HTTPClient(
            base_url="http://localhost:15672",
            username="guest",
            password="guest",
        )

        assert client.base_url == "http://localhost:15672/api"
        assert client.username == "guest"
        assert client.password == "guest"
        assert client.verify_cert is True
        assert client.timeout == DEFAULT_TIMEOUT
        assert client.client is not None

    def test_init_custom_parameters(self):
        """Test HTTPClient initialization with custom parameters (legacy interface)."""
        client = HTTPClient(
            base_url="https://rabbitmq.example.com:15672",
            username="admin",
            password="secret",
            verify_ssl=False,
            timeout=60,
            pool_size=10,
        )

        assert client.base_url == "https://rabbitmq.example.com:15672/api"
        assert client.username == "admin"
        assert client.password == "secret"
        assert client.verify_cert is False
        assert client.timeout == 60

    def test_build_auth_header(self):
        """Test HTTP Basic Auth header construction (legacy interface)."""
        client = HTTPClient(
            base_url="http://localhost:15672",
            username="guest",
            password="guest",
        )

        auth_header = client._build_auth_header()

        # Verify format
        assert auth_header.startswith("Basic ")

        # Verify encoding
        expected = base64.b64encode(b"guest:guest").decode()
        assert auth_header == f"Basic {expected}"

    @pytest.mark.asyncio
    async def test_request_get(self):
        """Test GET request execution (legacy interface)."""
        client = HTTPClient(
            base_url="http://localhost:15672",
            username="guest",
            password="guest",
        )

        # Mock httpx response
        mock_response = MagicMock(spec=httpx.Response)
        mock_response.status_code = 200
        mock_response.json.return_value = {"status": "ok"}

        with patch.object(
            client.client, "request", new_callable=AsyncMock
        ) as mock_request:
            mock_request.return_value = mock_response

            response = await client.request(
                method="GET",
                path="/overview",
            )

            assert response.status_code == 200
            assert response.json() == {"status": "ok"}

            # Verify request was made correctly
            mock_request.assert_called_once()
            call_kwargs = mock_request.call_args.kwargs
            assert call_kwargs["method"] == "GET"
            assert "api/overview" in call_kwargs["url"]
            assert "Authorization" in call_kwargs["headers"]
            assert call_kwargs["headers"]["Authorization"].startswith("Basic ")

    @pytest.mark.asyncio
    async def test_request_post_with_body(self):
        """Test POST request with JSON body (legacy interface)."""
        client = HTTPClient(
            base_url="http://localhost:15672",
            username="guest",
            password="guest",
        )

        mock_response = MagicMock(spec=httpx.Response)
        mock_response.status_code = 201

        with patch.object(
            client.client, "request", new_callable=AsyncMock
        ) as mock_request:
            mock_request.return_value = mock_response

            response = await client.request(
                method="POST",
                path="/queues/%2F/test-queue",
                json={"durable": True, "auto_delete": False},
            )

            assert response.status_code == 201

            # Verify Content-Type header added
            call_kwargs = mock_request.call_args.kwargs
            assert call_kwargs["headers"]["Content-Type"] == "application/json"
            assert call_kwargs["json"] == {"durable": True, "auto_delete": False}

    @pytest.mark.asyncio
    async def test_request_timeout(self):
        """Test request timeout handling (legacy interface)."""
        client = HTTPClient(
            base_url="http://localhost:15672",
            username="guest",
            password="guest",
            timeout=1,
        )

        with patch.object(
            client.client, "request", new_callable=AsyncMock
        ) as mock_request:
            mock_request.side_effect = httpx.TimeoutException("Request timed out")

            with pytest.raises(httpx.TimeoutException):
                await client.request(method="GET", path="/overview")

    @pytest.mark.asyncio
    async def test_request_connection_error(self):
        """Test connection error handling (legacy interface)."""
        client = HTTPClient(
            base_url="http://localhost:15672",
            username="guest",
            password="guest",
        )

        with patch.object(
            client.client, "request", new_callable=AsyncMock
        ) as mock_request:
            mock_request.side_effect = httpx.ConnectError("Connection refused")

            with pytest.raises(httpx.ConnectError):
                await client.request(method="GET", path="/overview")

    @pytest.mark.asyncio
    async def test_close(self):
        """Test HTTP client graceful close (legacy interface)."""
        client = HTTPClient(
            base_url="http://localhost:15672",
            username="guest",
            password="guest",
        )

        with patch.object(
            client.client, "aclose", new_callable=AsyncMock
        ) as mock_close:
            await client.close()
            mock_close.assert_called_once()

    def test_base_url_parsing(self):
        """Test base URL parsing from legacy interface."""
        client = HTTPClient(
            base_url="http://localhost:15672/",
            username="guest",
            password="guest",
        )

        assert "localhost" in client.base_url
        assert "15672" in client.base_url
        assert client.base_url.endswith("/api")


class TestHTTPClientReconnection:
    """Test suite for HTTPClient reconnection handling (Story 2.5 AC #6)."""

    @pytest.mark.asyncio
    async def test_request_during_reconnection_raises_error(self):
        """Test request raises ConnectionError when reconnecting (AC #6)."""
        from unittest.mock import Mock

        from rabbitmq_mcp_connection.schemas.connection import ConnectionState
        from rabbitmq_mcp_connection.schemas.reconnect import ReconnectionState

        # Create mock reconnection manager
        mock_reconnection_manager = Mock()
        mock_reconnection_manager.get_state.return_value = ReconnectionState(
            state=ConnectionState.RECONNECTING.value,
            attempt=3,
            next_attempt_in_seconds=8,
            total_downtime_seconds=15.0,
            last_reconnect_timestamp=None,
        )

        client = HTTPClientManager(
            host="localhost",
            port=15672,
            username="guest",
            password="guest",
            reconnection_manager=mock_reconnection_manager,
        )

        # Attempt request during reconnection should raise ConnectionError
        with pytest.raises(
            ConnectionError,
            match="Connection lost, please retry after reconnection",
        ):
            await client.request("GET", "/overview")

    @pytest.mark.asyncio
    async def test_request_succeeds_when_connected(self):
        """Test request succeeds when connection is established."""
        from unittest.mock import Mock

        from rabbitmq_mcp_connection.schemas.connection import ConnectionState
        from rabbitmq_mcp_connection.schemas.reconnect import ReconnectionState

        # Create mock reconnection manager in CONNECTED state
        mock_reconnection_manager = Mock()
        mock_reconnection_manager.get_state.return_value = ReconnectionState(
            state=ConnectionState.CONNECTED.value,
            attempt=0,
            next_attempt_in_seconds=None,
            total_downtime_seconds=0.0,
            last_reconnect_timestamp=None,
        )

        client = HTTPClientManager(
            host="localhost",
            port=15672,
            username="guest",
            password="guest",
            reconnection_manager=mock_reconnection_manager,
        )

        # Mock successful HTTP response
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.headers = {}
        mock_response.text = "{}"
        mock_response.json.return_value = {}

        with patch.object(client.client, "request", return_value=mock_response):
            response = await client.request("GET", "/overview")
            assert response.status_code == 200

    @pytest.mark.asyncio
    async def test_request_without_reconnection_manager(self):
        """Test request works normally without reconnection manager."""
        # Client without reconnection manager
        client = HTTPClientManager(
            host="localhost",
            port=15672,
            username="guest",
            password="guest",
        )

        # Mock successful HTTP response
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.headers = {}
        mock_response.text = "{}"
        mock_response.json.return_value = {}

        with patch.object(client.client, "request", return_value=mock_response):
            response = await client.request("GET", "/overview")
            assert response.status_code == 200
