import json
import logging
from uuid import uuid4

from .base import BaseBroker
from ..conf import devicehub_settings
from ..exceptions import BrokerError

logger = logging.getLogger("django_devicehub.broker")


class MQTTBroker(BaseBroker):
    """MQTT 5.0 broker implementation using paho-mqtt.

    Supports shared subscriptions, TLS, MQTT 5 properties,
    and automatic reconnection.
    """

    def __init__(self, name="default", config=None):
        if config is None:
            config = devicehub_settings.BROKERS.get(name, {})
        super().__init__(name, config)
        self._client = None
        self._message_handler = None

    def _get_client(self):
        if self._client is not None:
            return self._client

        try:
            import paho.mqtt.client as mqtt
        except ImportError:
            raise BrokerError(
                "paho-mqtt is required for MQTT support. "
                "Install it with: pip install django-devicehub[mqtt]"
            )

        client_id = (
            self.config.get("CLIENT_ID") or f"django-devicehub-{uuid4().hex[:8]}"
        )
        self._client = mqtt.Client(
            client_id=client_id,
            protocol=mqtt.MQTTv5,
            callback_api_version=mqtt.CallbackAPIVersion.VERSION2,
        )

        # Authentication
        username = self.config.get("USERNAME")
        password = self.config.get("PASSWORD")
        if username:
            self._client.username_pw_set(username, password)

        # TLS
        if self.config.get("USE_TLS"):
            self._client.tls_set(
                ca_certs=self.config.get("CA_CERTS") or None,
                certfile=self.config.get("CERTFILE") or None,
                keyfile=self.config.get("KEYFILE") or None,
            )

        # Callbacks
        self._client.on_connect = self._on_connect
        self._client.on_message = self._on_message
        self._client.on_disconnect = self._on_disconnect

        return self._client

    def set_message_handler(self, handler):
        """Set the callable that processes incoming messages.

        The handler receives a paho MQTTMessage object.
        """
        self._message_handler = handler

    def connect(self):
        import paho.mqtt.properties as properties
        import paho.mqtt.packettypes as packettypes

        client = self._get_client()
        host = self.config.get("HOST", "localhost")
        port = self.config.get("PORT", 1883)
        keepalive = self.config.get("KEEPALIVE", 60)

        connect_props = properties.Properties(packettypes.PacketTypes.CONNECT)
        connect_props.SessionExpiryInterval = 300

        try:
            client.connect(host, port, keepalive=keepalive, properties=connect_props)
            logger.info("Connecting to MQTT broker at %s:%s", host, port)
        except Exception as e:
            raise BrokerError(f"Failed to connect to MQTT broker: {e}")

    def disconnect(self):
        if self._client:
            self._client.disconnect()
            self._connected = False
            logger.info("Disconnected from MQTT broker")

    def subscribe(self, topic, qos=1):
        client = self._get_client()
        use_shared = self.config.get("USE_SHARED_SUBSCRIPTIONS", True)

        if use_shared:
            share_group = self.config.get("SHARE_GROUP", "django-devicehub")
            shared_topic = f"$share/{share_group}/{topic}"
            client.subscribe(shared_topic, qos=qos)
            logger.debug("Subscribed (shared): %s", shared_topic)
        else:
            client.subscribe(topic, qos=qos)
            logger.debug("Subscribed: %s", topic)

    def publish(self, topic, payload, qos=1, **kwargs):
        import paho.mqtt.properties as properties
        import paho.mqtt.packettypes as packettypes

        client = self._get_client()

        pub_props = properties.Properties(packettypes.PacketTypes.PUBLISH)
        pub_props.ContentType = "application/json"

        if "correlation_id" in kwargs:
            pub_props.CorrelationData = kwargs["correlation_id"].encode()
        if "user_properties" in kwargs:
            for k, v in kwargs["user_properties"].items():
                pub_props.UserProperty = (k, str(v))

        data = (
            json.dumps(payload).encode() if not isinstance(payload, bytes) else payload
        )
        result = client.publish(topic, data, qos=qos, properties=pub_props)
        if result.rc != 0:
            logger.error("Failed to publish to %s: rc=%s", topic, result.rc)
        return result

    def loop_forever(self):
        self._get_client().loop_forever()

    def loop_start(self):
        self._get_client().loop_start()

    def loop_stop(self):
        self._get_client().loop_stop()

    # -- Callbacks --

    def _on_connect(self, client, userdata, flags, rc, properties=None):
        if rc == 0:
            self._connected = True
            logger.info("Connected to MQTT broker")
            self._subscribe_all()
        else:
            logger.error("MQTT connection failed: rc=%s", rc)

    def _on_message(self, client, userdata, message):
        if self._message_handler:
            try:
                self._message_handler(message)
            except Exception:
                logger.exception("Error handling message on %s", message.topic)

    def _on_disconnect(self, client, userdata, flags, rc, properties=None):
        self._connected = False
        if rc != 0:
            logger.warning("Unexpected MQTT disconnect: rc=%s", rc)
        else:
            logger.info("MQTT disconnected cleanly")

    def _subscribe_all(self):
        """Subscribe to all registered device type topics."""
        from ..registry import registry

        for device_type in registry.all():
            if device_type._device_model is None:
                continue
            for topic in device_type.get_subscribe_topics():
                self.subscribe(topic)
