"""Unit tests for AMQP ConnectionManager TLS integration."""

from __future__ import annotations

import asyncio
import ssl
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from rabbitmq_mcp_connection.connection.exceptions import (
    ConnectionTimeoutError,
    SSLCertificateVerificationError,
)
from rabbitmq_mcp_connection.connection.manager import ConnectionManager
from rabbitmq_mcp_connection.schemas.connection import ConnectionConfig


class TestConnectionManagerTLS:
    """Test ConnectionManager TLS integration (AC #1, #6, #7, #8)."""

    @pytest.fixture
    def tls_config(self):
        """Create ConnectionConfig with TLS enabled."""
        return ConnectionConfig(
            host="localhost",
            port=5671,
            user="guest",
            password="guest",
            vhost="/",
            use_tls=True,
            verify_cert=True,
            ca_bundle=None,
            timeout=30,
        )

    @pytest.fixture
    def insecure_tls_config(self):
        """Create ConnectionConfig with TLS enabled but verification disabled."""
        return ConnectionConfig(
            host="localhost",
            port=5671,
            user="guest",
            password="guest",
            vhost="/",
            use_tls=True,
            verify_cert=False,
            ca_bundle=None,
            timeout=30,
        )

    @pytest.fixture
    def no_tls_config(self):
        """Create ConnectionConfig with TLS disabled."""
        return ConnectionConfig(
            host="localhost",
            port=5672,
            user="guest",
            password="guest",
            vhost="/",
            use_tls=False,
            verify_cert=True,
            ca_bundle=None,
            timeout=30,
        )

    @pytest.mark.asyncio
    async def test_connect_with_tls_enabled_creates_ssl_context(self, tls_config):
        """Test that SSL context is created when TLS enabled (AC #1)."""
        manager = ConnectionManager(tls_config)

        with (
            patch(
                "rabbitmq_mcp_connection.connection.manager.TLSContextManager.create_ssl_context"
            ) as mock_create_ssl,
            patch(
                "rabbitmq_mcp_connection.connection.manager.aio_pika.connect_robust",
                new_callable=AsyncMock,
            ) as mock_connect,
        ):
            mock_ssl_context = MagicMock(spec=ssl.SSLContext)
            mock_create_ssl.return_value = mock_ssl_context
            mock_connection = AsyncMock()
            mock_connection.channel = AsyncMock()
            mock_connect.return_value = mock_connection

            await manager.connect()

            # Verify SSL context was created with correct parameters
            mock_create_ssl.assert_called_once_with(
                verify_cert=True,
                ca_bundle=None,
            )

            # Verify connect_robust was called with SSL context
            assert mock_connect.call_args.kwargs["ssl"] is True
            assert mock_connect.call_args.kwargs["ssl_context"] == mock_ssl_context

    @pytest.mark.asyncio
    async def test_connect_with_tls_disabled_logs_warning(self, no_tls_config, caplog):
        """Test that warning is logged when TLS disabled (AC #8)."""
        import logging

        caplog.set_level(logging.WARNING)
        manager = ConnectionManager(no_tls_config)

        with patch(
            "rabbitmq_mcp_connection.connection.manager.aio_pika.connect_robust",
            new_callable=AsyncMock,
        ) as mock_connect:
            mock_connection = AsyncMock()
            mock_connection.channel = AsyncMock()
            mock_connect.return_value = mock_connection

            await manager.connect()

            # Verify TLS disabled warning was logged
            assert len(caplog.records) > 0

    @pytest.mark.asyncio
    async def test_connect_ssl_handshake_timeout(self, tls_config):
        """Test TLS handshake timeout detection (AC #7)."""
        manager = ConnectionManager(tls_config)

        with (
            patch(
                "rabbitmq_mcp_connection.connection.manager.TLSContextManager.create_ssl_context"
            ) as mock_create_ssl,
            patch(
                "rabbitmq_mcp_connection.connection.manager.aio_pika.connect_robust",
                new_callable=AsyncMock,
            ) as mock_connect,
        ):
            mock_create_ssl.return_value = MagicMock(spec=ssl.SSLContext)
            # Simulate timeout during TLS handshake
            mock_connect.side_effect = TimeoutError()

            with pytest.raises(ConnectionTimeoutError) as exc_info:
                await manager.connect()

            # Verify error message mentions TLS handshake (case insensitive)
            error_msg = str(exc_info.value).lower()
            assert (
                "tls handshake timeout" in error_msg or "handshake timeout" in error_msg
            )

    @pytest.mark.asyncio
    async def test_connect_ssl_certificate_verification_failed(self, tls_config):
        """Test SSL certificate verification failure (AC #6)."""
        manager = ConnectionManager(tls_config)

        with (
            patch(
                "rabbitmq_mcp_connection.connection.manager.TLSContextManager.create_ssl_context"
            ) as mock_create_ssl,
            patch(
                "rabbitmq_mcp_connection.connection.manager.aio_pika.connect_robust",
                new_callable=AsyncMock,
            ) as mock_connect,
        ):
            mock_create_ssl.return_value = MagicMock(spec=ssl.SSLContext)
            # Simulate SSL certificate verification failure
            ssl_error = ssl.SSLError(
                "certificate verify failed: certificate has expired"
            )
            mock_connect.side_effect = ssl_error

            with pytest.raises(SSLCertificateVerificationError) as exc_info:
                await manager.connect()

            # Verify error message contains details
            assert "certificate" in str(exc_info.value).lower()
            assert exc_info.value.details is not None

    @pytest.mark.asyncio
    async def test_connect_ssl_expired_certificate(self, tls_config):
        """Test expired certificate detection (AC #6)."""
        manager = ConnectionManager(tls_config)

        with (
            patch(
                "rabbitmq_mcp_connection.connection.manager.TLSContextManager.create_ssl_context"
            ) as mock_create_ssl,
            patch(
                "rabbitmq_mcp_connection.connection.manager.aio_pika.connect_robust",
                new_callable=AsyncMock,
            ) as mock_connect,
        ):
            mock_create_ssl.return_value = MagicMock(spec=ssl.SSLContext)
            ssl_error = ssl.SSLError(
                "certificate verify failed: certificate has expired"
            )
            mock_connect.side_effect = ssl_error

            with pytest.raises(SSLCertificateVerificationError) as exc_info:
                await manager.connect()

            # Verify error details mention expiration
            assert "expired" in exc_info.value.details.lower()

    @pytest.mark.asyncio
    async def test_connect_ssl_hostname_mismatch(self, tls_config):
        """Test hostname mismatch detection (AC #6)."""
        manager = ConnectionManager(tls_config)

        with (
            patch(
                "rabbitmq_mcp_connection.connection.manager.TLSContextManager.create_ssl_context"
            ) as mock_create_ssl,
            patch(
                "rabbitmq_mcp_connection.connection.manager.aio_pika.connect_robust",
                new_callable=AsyncMock,
            ) as mock_connect,
        ):
            mock_create_ssl.return_value = MagicMock(spec=ssl.SSLContext)
            ssl_error = ssl.SSLError("certificate verify failed: hostname mismatch")
            mock_connect.side_effect = ssl_error

            with pytest.raises(SSLCertificateVerificationError) as exc_info:
                await manager.connect()

            # Verify error details mention hostname
            assert (
                "hostname" in exc_info.value.details.lower()
                or "mismatch" in exc_info.value.details.lower()
            )

    @pytest.mark.asyncio
    async def test_connect_ssl_untrusted_ca(self, tls_config):
        """Test untrusted CA detection (AC #6)."""
        manager = ConnectionManager(tls_config)

        with (
            patch(
                "rabbitmq_mcp_connection.connection.manager.TLSContextManager.create_ssl_context"
            ) as mock_create_ssl,
            patch(
                "rabbitmq_mcp_connection.connection.manager.aio_pika.connect_robust",
                new_callable=AsyncMock,
            ) as mock_connect,
        ):
            mock_create_ssl.return_value = MagicMock(spec=ssl.SSLContext)
            ssl_error = ssl.SSLError(
                "certificate verify failed: self signed certificate"
            )
            mock_connect.side_effect = ssl_error

            with pytest.raises(SSLCertificateVerificationError) as exc_info:
                await manager.connect()

            # Verify error details mention self-signed or untrusted
            details_lower = exc_info.value.details.lower()
            assert "self" in details_lower or "untrusted" in details_lower

    @pytest.mark.asyncio
    async def test_connect_with_custom_ca_bundle(self, tmp_path):
        """Test connection with custom CA bundle (AC #5)."""
        ca_file = tmp_path / "ca.pem"
        ca_file.write_text("FAKE CA CERT")

        config = ConnectionConfig(
            host="localhost",
            port=5671,
            user="guest",
            password="guest",
            vhost="/",
            use_tls=True,
            verify_cert=True,
            ca_bundle=str(ca_file),
            timeout=30,
        )
        manager = ConnectionManager(config)

        with (
            patch(
                "rabbitmq_mcp_connection.connection.manager.TLSContextManager.create_ssl_context"
            ) as mock_create_ssl,
            patch(
                "rabbitmq_mcp_connection.connection.manager.aio_pika.connect_robust",
                new_callable=AsyncMock,
            ) as mock_connect,
        ):
            mock_create_ssl.return_value = MagicMock(spec=ssl.SSLContext)
            mock_connection = AsyncMock()
            mock_connection.channel = AsyncMock()
            mock_connect.return_value = mock_connection

            await manager.connect()

            # Verify CA bundle was passed to SSL context creation
            mock_create_ssl.assert_called_once_with(
                verify_cert=True,
                ca_bundle=str(ca_file),
            )
