from __future__ import annotations
from typing import TYPE_CHECKING, Any, Callable, Optional

import pika
from mockmesh.core.fallback import FallbackConfig
from mockmesh.core.logger import get_logger
from mockmesh.plugins.base import BasePlugin

if TYPE_CHECKING:
    from mockmesh.response.engine import ResponseEngine

_log = get_logger("interceptors.rabbitmq")


_active_rabbitmq_rules: list = []


def _match_rabbitmq_rule(queue: str = "", exchange: str = "") -> dict:
    for rule in _active_rabbitmq_rules:
        m = rule.get("match", {})
        if m.get("queue", "*") in ("*", queue) and m.get("exchange", "*") in ("*", exchange):
            return rule
    return {}


class _MockMethod:

    def __init__(self, delivery_tag: int = 1, routing_key: str = "", exchange: str = "") -> None:
        self.delivery_tag = delivery_tag
        self.routing_key = routing_key
        self.exchange = exchange
        self.redelivered = False


class _MockProperties:

    def __init__(self) -> None:
        self.content_type = "application/json"
        self.delivery_mode = 1
        self.headers: dict = {}


class _MockChannel:
    def __init__(self, response_engine: "ResponseEngine") -> None:
        self._re = response_engine

    def queue_declare(self, queue: str = "", **kwargs: Any) -> Any:
        _log.debug("RabbitMQ mock: queue_declare", extra={"mm_queue": queue})

        class _DeclareOk:
            method = type("Method", (), {"queue": queue, "message_count": 0, "consumer_count": 0})()

        return _DeclareOk()

    def exchange_declare(self, exchange: str = "", exchange_type: str = "direct", **kwargs: Any) -> None:
        _log.debug("RabbitMQ mock: exchange_declare", extra={"mm_exchange": exchange})

    def queue_bind(self, queue: str = "", exchange: str = "", routing_key: str = "", **kwargs: Any) -> None:
        _log.debug("RabbitMQ mock: queue_bind")

    def basic_publish(
        self,
        exchange: str = "",
        routing_key: str = "",
        body: Any = "",
        properties: Any = None,
        mandatory: bool = False,
    ) -> None:
        rule = _match_rabbitmq_rule(queue=routing_key, exchange=exchange)
        pr = rule.get("response", {}).get("publish") or rule.get("publish_response", {"delivery_tag": 1})
        _log.info(
            "Intercepted RabbitMQ publish",
            extra={
                "mm_exchange": exchange,
                "mm_routing_key": routing_key,
                "mm_delivery_tag": pr.get("delivery_tag"),
            },
        )

    def basic_get(
        self, queue: str = "", auto_ack: bool = False
    ) -> tuple[Optional[Any], Optional[Any], Optional[bytes]]:
        rule = _match_rabbitmq_rule(queue=queue)
        cr = rule.get("response", {}).get("consume") or rule.get("consume_response", {})
        if not cr:
            return None, None, None
        _log.info("Intercepted RabbitMQ basic_get", extra={"mm_queue": queue})
        method = _MockMethod(
            delivery_tag=cr.get("delivery_tag", 1),
            routing_key=cr.get("routing_key", ""),
            exchange=cr.get("exchange", ""),
        )
        body_str = cr.get("body", "{}")
        return method, _MockProperties(), body_str.encode()

    def basic_consume(
        self,
        queue: str = "",
        on_message_callback: Optional[Callable] = None,
        auto_ack: bool = False,
        **kwargs: Any,
    ) -> str:
        _log.debug("RabbitMQ mock: basic_consume registered", extra={"mm_queue": queue})
        self._pending_consume = (queue, on_message_callback)
        return "mock-consumer-tag"

    def start_consuming(self) -> None:
        if not hasattr(self, "_pending_consume"):
            return
        queue, cb = self._pending_consume
        rule = _match_rabbitmq_rule(queue=queue)
        cr = rule.get("response", {}).get("consume") or rule.get("consume_response", {})
        if cb and cr:
            method = _MockMethod(
                delivery_tag=cr.get("delivery_tag", 1),
                routing_key=cr.get("routing_key", ""),
                exchange=cr.get("exchange", ""),
            )
            body = cr.get("body", "{}").encode()
            _log.info("Intercepted RabbitMQ start_consuming", extra={"mm_queue": queue})
            cb(self, method, _MockProperties(), body)

    def basic_ack(self, delivery_tag: int = 0, multiple: bool = False) -> None:
        _log.debug("RabbitMQ mock: basic_ack", extra={"mm_delivery_tag": delivery_tag})

    def basic_nack(self, delivery_tag: int = 0, requeue: bool = True, **kwargs: Any) -> None:
        _log.debug("RabbitMQ mock: basic_nack", extra={"mm_delivery_tag": delivery_tag})

    def close(self) -> None:
        pass

    @property
    def is_open(self) -> bool:
        return True


class _MockBlockingConnection:
    def __init__(self, parameters: Any = None, response_engine: "ResponseEngine" = None, **kwargs: Any) -> None:  # type: ignore
        self._re = response_engine
        _log.debug("RabbitMQ mock: BlockingConnection created (no TCP)")

    def channel(self) -> _MockChannel:
        return _MockChannel(self._re)

    def close(self) -> None:
        pass

    @property
    def is_open(self) -> bool:
        return True

    def __enter__(self) -> "_MockBlockingConnection":
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()


class RabbitMQInterceptor(BasePlugin):

    name = "rabbitmq"

    def __init__(self, response_engine: "ResponseEngine") -> None:
        super().__init__(response_engine)
        self._original_connection: Any = None

    def activate(
        self,
        config: dict,
        *,
        storage: "Any | None" = None,
        provider_defaults: Optional[dict] = None,
        fallback_config: Optional[FallbackConfig] = None,
        custom_handlers: Optional[dict] = None,
        **_: Any,
    ) -> None:
        self._fallback_config = fallback_config
        self._custom_handlers = custom_handlers if custom_handlers is not None else {}
        global _active_rabbitmq_rules
        if provider_defaults is not None:
            rabbitmq_rules = provider_defaults.get("rabbitmq")
            if rabbitmq_rules is not None:
                _active_rabbitmq_rules = rabbitmq_rules

        self._patch_pika()
        self._active = True
        _log.info("RabbitMQ interceptor activated")

    def deactivate(self) -> None:
        if not self._active:
            return
        self._unpatch_pika()
        self._active = False
        _log.info("RabbitMQ interceptor deactivated")

    def _patch_pika(self) -> None:
        re = self.response_engine
        self._original_connection = pika.BlockingConnection

        class _Connection(_MockBlockingConnection):
            def __init__(self, parameters: Any = None, **kwargs: Any) -> None:
                super().__init__(parameters=parameters, response_engine=re, **kwargs)

        pika.BlockingConnection = _Connection  # type: ignore
        _log.debug("Patched pika.BlockingConnection")

    def _unpatch_pika(self) -> None:
        if self._original_connection is None:
            return
        pika.BlockingConnection = self._original_connection
        self._original_connection = None
        _log.debug("Restored pika.BlockingConnection")
