"""Tests unitaires pour RabbitMQPublisher."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest
from pydantic import BaseModel
from pika.exceptions import AMQPConnectionError, AMQPChannelError

from cezam_lib.cezam_shared.rabbitmq import RabbitMQPublisher
from cezam_lib.cezam_shared.exceptions import RabbitMQError


class SampleMessage(BaseModel):
    """Message de test."""

    id: str
    value: int


class TestRabbitMQPublisherInit:
    """Tests d'initialisation du publisher."""

    @patch("cezam_lib.cezam_shared.rabbitmq.pika.BlockingConnection")
    def test_init_success(self, mock_connection):
        """Le publisher se connecte correctement."""
        mock_channel = MagicMock()
        mock_connection.return_value.channel.return_value = mock_channel

        publisher = RabbitMQPublisher(
            host="localhost",
            port=5672,
            user="guest",
            password="guest",
        )

        assert publisher._connection is not None
        assert publisher._channel is not None
        mock_connection.assert_called_once()

    @patch("cezam_lib.cezam_shared.rabbitmq.pika.BlockingConnection")
    def test_init_connection_error(self, mock_connection):
        """Une erreur de connexion lève RabbitMQError."""
        mock_connection.side_effect = AMQPConnectionError("Connection refused")

        with pytest.raises(RabbitMQError) as exc_info:
            RabbitMQPublisher(
                host="localhost",
                port=5672,
                user="guest",
                password="guest",
            )

        assert "Échec de connexion à RabbitMQ" in str(exc_info.value)


class TestRabbitMQPublisherPublish:
    """Tests de publication de messages."""

    @patch("cezam_lib.cezam_shared.rabbitmq.pika.BlockingConnection")
    def test_publish_dict_message(self, mock_connection):
        """Publication d'un dict fonctionne."""
        mock_channel = MagicMock()
        mock_connection.return_value.channel.return_value = mock_channel
        mock_connection.return_value.is_closed = False
        mock_channel.is_closed = False

        publisher = RabbitMQPublisher(
            host="localhost",
            port=5672,
            user="guest",
            password="guest",
        )

        message = {"id": "test-123", "value": 42}
        publisher.publish(
            exchange="test.exchange",
            routing_key="test.key",
            message=message,
        )

        mock_channel.basic_publish.assert_called_once()
        call_kwargs = mock_channel.basic_publish.call_args.kwargs
        assert call_kwargs["exchange"] == "test.exchange"
        assert call_kwargs["routing_key"] == "test.key"
        assert b'"id": "test-123"' in call_kwargs["body"]

    @patch("cezam_lib.cezam_shared.rabbitmq.pika.BlockingConnection")
    def test_publish_pydantic_message(self, mock_connection):
        """Publication d'un Pydantic model fonctionne."""
        mock_channel = MagicMock()
        mock_connection.return_value.channel.return_value = mock_channel
        mock_connection.return_value.is_closed = False
        mock_channel.is_closed = False

        publisher = RabbitMQPublisher(
            host="localhost",
            port=5672,
            user="guest",
            password="guest",
        )

        message = SampleMessage(id="test-456", value=100)
        publisher.publish(
            exchange="test.exchange",
            routing_key="test.key",
            message=message,
        )

        mock_channel.basic_publish.assert_called_once()
        call_kwargs = mock_channel.basic_publish.call_args.kwargs
        assert b'"id":"test-456"' in call_kwargs["body"]

    @patch("cezam_lib.cezam_shared.rabbitmq.pika.BlockingConnection")
    def test_publish_sets_persistent_delivery_mode(self, mock_connection):
        """Les messages sont publiés en mode persistent."""
        mock_channel = MagicMock()
        mock_connection.return_value.channel.return_value = mock_channel
        mock_connection.return_value.is_closed = False
        mock_channel.is_closed = False

        publisher = RabbitMQPublisher(
            host="localhost",
            port=5672,
            user="guest",
            password="guest",
        )

        publisher.publish(
            exchange="test.exchange",
            routing_key="test.key",
            message={"test": "data"},
        )

        call_kwargs = mock_channel.basic_publish.call_args.kwargs
        properties = call_kwargs["properties"]
        assert properties.delivery_mode == 2
        assert properties.content_type == "application/json"

    @patch("cezam_lib.cezam_shared.rabbitmq.pika.BlockingConnection")
    def test_publish_channel_error(self, mock_connection):
        """Une erreur de channel lève RabbitMQError."""
        mock_channel = MagicMock()
        mock_connection.return_value.channel.return_value = mock_channel
        mock_connection.return_value.is_closed = False
        mock_channel.is_closed = False
        mock_channel.basic_publish.side_effect = AMQPChannelError("Channel closed")

        publisher = RabbitMQPublisher(
            host="localhost",
            port=5672,
            user="guest",
            password="guest",
        )

        with pytest.raises(RabbitMQError) as exc_info:
            publisher.publish(
                exchange="test.exchange",
                routing_key="test.key",
                message={"test": "data"},
            )

        assert "Échec de publication" in str(exc_info.value)


class TestRabbitMQPublisherOTelPropagation:
    """Tests de propagation du contexte OTel."""

    @patch("cezam_lib.cezam_shared.rabbitmq.inject")
    @patch("cezam_lib.cezam_shared.rabbitmq.pika.BlockingConnection")
    def test_publish_injects_otel_context(self, mock_connection, mock_inject):
        """Le contexte OTel est injecté dans les headers."""
        mock_channel = MagicMock()
        mock_connection.return_value.channel.return_value = mock_channel
        mock_connection.return_value.is_closed = False
        mock_channel.is_closed = False

        def inject_traceparent(headers):
            headers["traceparent"] = "00-trace-span-01"

        mock_inject.side_effect = inject_traceparent

        publisher = RabbitMQPublisher(
            host="localhost",
            port=5672,
            user="guest",
            password="guest",
        )

        publisher.publish(
            exchange="test.exchange",
            routing_key="test.key",
            message={"test": "data"},
        )

        mock_inject.assert_called_once()
        call_kwargs = mock_channel.basic_publish.call_args.kwargs
        properties = call_kwargs["properties"]
        assert properties.headers == {"traceparent": "00-trace-span-01"}


class TestRabbitMQPublisherClose:
    """Tests de fermeture de connexion."""

    @patch("cezam_lib.cezam_shared.rabbitmq.pika.BlockingConnection")
    def test_close_closes_connection(self, mock_connection):
        """close() ferme la connexion."""
        mock_conn_instance = MagicMock()
        mock_conn_instance.is_closed = False
        mock_connection.return_value = mock_conn_instance

        publisher = RabbitMQPublisher(
            host="localhost",
            port=5672,
            user="guest",
            password="guest",
        )

        publisher.close()

        mock_conn_instance.close.assert_called_once()
        assert publisher._connection is None
        assert publisher._channel is None

    @patch("cezam_lib.cezam_shared.rabbitmq.pika.BlockingConnection")
    def test_context_manager(self, mock_connection):
        """Le context manager ferme la connexion."""
        mock_conn_instance = MagicMock()
        mock_conn_instance.is_closed = False
        mock_connection.return_value = mock_conn_instance

        with RabbitMQPublisher(
            host="localhost",
            port=5672,
            user="guest",
            password="guest",
        ) as publisher:
            assert publisher._connection is not None

        mock_conn_instance.close.assert_called_once()
