from __future__ import annotations

import json

from django.conf import settings
from pika import BasicProperties, BlockingConnection, ConnectionParameters, PlainCredentials
from pika.channel import Channel
from pika.connection import Connection


class ConnectionDescriptor:
    def __init__(self, host: str, port: int, username: str, password: str):
        self.host = host
        self.port = port
        self.username = username
        self.password = password

    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, instance, cls: type[BaseQueue]):
        if instance is None:
            assert all([self.host, self.port, self.username, self.password])

            credentials = PlainCredentials(username=self.username, password=self.password)
            parameters = ConnectionParameters(host=self.host, port=self.port, credentials=credentials)
            connection = BlockingConnection(parameters=parameters)
            setattr(cls, self.name, connection)
        raise RuntimeError("Cannot access class attributes from concrete instance.")


class ChannelDescriptor:
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, instance, cls: type[BaseQueue]):
        if instance is None:
            channel = cls._connection.channel()
            setattr(cls, self.name, channel)
        raise RuntimeError("Cannot access class attributes from concrete instance.")


class BaseQueue:
    _connection: Connection = ConnectionDescriptor(
        host=getattr(settings, "RABBITMQ_BROKER_HOST", None),
        port=getattr(settings, "RABBITMQ_BROKER_PORT", None),
        username=getattr(settings, "RABBITMQ_BROKER_USERNAME", None),
        password=getattr(settings, "RABBITMQ_BROKER_PASSWORD", None),
    )
    _channel: Channel = ChannelDescriptor()
    _exchange: str

    @classmethod
    def publish(
        cls,
        data: dict,
        queue: str,
        exchange: str = "",
        mandatory: bool = False,
        properties: BasicProperties = None,
        **kwargs,
    ):
        data = data or {}
        exchange = exchange or cls._exchange
        body = json.dumps(data).encode("utf-8")
        properties = properties or BasicProperties(content_type="application/json", content_encoding="utf-8", **kwargs)

        cls._channel.basic_publish(
            body=body,
            exchange=exchange,
            routing_key=queue,
            properties=properties,
            mandatory=mandatory,
        )
