import json
import logging
from typing import Any

import aio_pika
from aio_pika import ExchangeType, Message, RobustConnection

from src.infrastructure.config.settings import settings

logger = logging.getLogger(__name__)


class RabbitMQClient:
    PIP_EXCHANGE = "pip.events"
    PIP_DLX = "pip.dlx"

    ROUTING_KEYS = {
        "inventory_updated": "inventory.updated",
        "reservation_created": "reservation.created",
        "reservation_confirmed": "reservation.confirmed",
        "reservation_cancelled": "reservation.cancelled",
        "reservation_expired": "reservation.expired",
        "sync_started": "sync.started",
        "sync_completed": "sync.completed",
        "sync_failed": "sync.failed",
        "sync_cancelled": "sync.cancelled",
        "sync_created": "sync.created",
        "connector_heartbeat": "connector.heartbeat",
        "connector_error": "connector.error",
        "connector_created": "connector.created",
        "connector_updated": "connector.updated",
        "connector_deleted": "connector.deleted",
        "connector_deployed": "connector.deployed",
        "connector_deactivated": "connector.deactivated",
        "pharmacy_updated": "pharmacy.updated",
        "medication_created": "medication.created",
        "medication_updated": "medication.updated",
        "medication_deleted": "medication.deleted",
        "catalog_upserted": "catalog.upserted",
    }

    def __init__(self) -> None:
        self._connection: RobustConnection | None = None
        self._channel: aio_pika.RobustChannel | None = None
        self._exchange: aio_pika.RobustExchange | None = None
        self._dlx: aio_pika.RobustExchange | None = None

    async def connect(self) -> None:
        self._connection = await aio_pika.connect_robust(settings.RABBITMQ_URL)
        self._channel = await self._connection.channel()
        await self._channel.set_qos(prefetch_count=100)

        self._exchange = await self._channel.declare_exchange(
            self.PIP_EXCHANGE,
            ExchangeType.TOPIC,
            durable=True,
        )

        self._dlx = await self._channel.declare_exchange(
            self.PIP_DLX,
            ExchangeType.TOPIC,
            durable=True,
        )

        logger.info("rabbitmq_connected", extra={"url": settings.RABBITMQ_URL})

    async def disconnect(self) -> None:
        if self._connection:
            await self._connection.close()

    async def publish(self, routing_key: str, payload: dict[str, Any], headers: dict[str, str] | None = None) -> None:
        if not self._exchange:
            raise RuntimeError("RabbitMQ not connected. Call connect() first.")

        message = Message(
            body=json.dumps(payload, default=str).encode(),
            content_type="application/json",
            delivery_mode=aio_pika.DeliveryMode.PERSISTENT,
            headers=headers or {},
        )

        await self._exchange.publish(message, routing_key=routing_key)

        logger.debug(
            "message_published",
            extra={"routing_key": routing_key},
        )

    async def consume(
        self,
        queue_name: str,
        routing_keys: list[str],
        callback: Any,
        dlq: bool = True,
    ) -> aio_pika.RobustQueue:
        if not self._channel or not self._exchange:
            raise RuntimeError("RabbitMQ not connected. Call connect() first.")

        queue_arguments = {
            "x-dead-letter-exchange": self.PIP_DLX,
            "x-dead-letter-routing-key": queue_name,
        }

        queue = await self._channel.declare_queue(
            queue_name,
            durable=True,
            arguments=queue_arguments,
        )

        for routing_key in routing_keys:
            await queue.bind(self._exchange, routing_key=routing_key)

        if dlq and self._dlx:
            dlq_name = f"{queue_name}.dlq"
            dlq = await self._channel.declare_queue(dlq_name, durable=True)
            await dlq.bind(self._dlx, routing_key=queue_name)

        await queue.consume(callback)

        return queue

    async def health_check(self) -> bool:
        try:
            if not self._connection or self._connection.is_closed:
                return False
            return True
        except Exception:
            return False


rabbitmq_client = RabbitMQClient()
