import inspect

from .fields import Command, Reading, StateField
from .registry import registry


class DeviceTypeMeta(type):
    """Metaclass that collects Reading, Command, and StateField descriptors
    from the class namespace and stores them as _readings, _commands, _state_fields.
    """

    def __new__(mcs, name, bases, namespace):
        readings = {}
        commands = {}
        state_fields = {}

        # Inherit from base classes
        for base in bases:
            if hasattr(base, "_readings"):
                readings.update(base._readings)
            if hasattr(base, "_commands"):
                commands.update(base._commands)
            if hasattr(base, "_state_fields"):
                state_fields.update(base._state_fields)

        # Collect from current class (overrides bases)
        for key, value in list(namespace.items()):
            if isinstance(value, Reading):
                value.name = key
                readings[key] = value
                del namespace[key]
            elif isinstance(value, Command):
                value.name = key
                commands[key] = value
                del namespace[key]
            elif isinstance(value, StateField):
                value.name = key
                state_fields[key] = value
                del namespace[key]

        namespace["_readings"] = readings
        namespace["_commands"] = commands
        namespace["_state_fields"] = state_fields

        cls = super().__new__(mcs, name, bases, namespace)

        # Register non-base classes
        if name != "DeviceType" and not getattr(cls, "_abstract", False):
            registry.register(cls)

        return cls


class DeviceType(metaclass=DeviceTypeMeta):
    """Base class for declaring IoT device types.

    Usage::

        # devices.py
        from django_devicehub import DeviceType, Reading, Command, reading_types

        class WeatherStation(DeviceType):
            class Meta:
                protocol = "mqtt"
                broker = "default"
                heartbeat_interval = 300

            temperature = Reading(type=reading_types.FLOAT, unit="C")
            humidity = Reading(type=reading_types.FLOAT, unit="%")

            reboot = Command()
            set_interval = Command(payload={"interval": int})

        # models.py
        from .devices import WeatherStation
        WeatherStationDevice, WeatherStationReading, WeatherStationMessage = (
            WeatherStation.create_models()
        )
    """

    # Set by create_models()
    _device_model = None
    _reading_model = None
    _message_model = None

    class Meta:
        protocol = "mqtt"
        broker = "default"
        auth = "token"
        heartbeat_interval = 300
        topic_template = "{prefix}/{type_name}/{device_id}/{message_type}"

    @classmethod
    def create_models(cls, module=None):
        """Generate concrete Django models for this DeviceType.

        Call this in your app's models.py. Returns (DeviceModel, ReadingModel, MessageModel).
        """
        if module is None:
            frame = inspect.currentframe().f_back
            module = frame.f_globals["__name__"]
        from .generators.models import create_models

        return create_models(cls, calling_module=module)

    @classmethod
    def get_meta(cls):
        """Get Meta configuration with defaults."""
        defaults = {
            "protocol": "mqtt",
            "broker": "default",
            "auth": "token",
            "heartbeat_interval": 300,
            "topic_template": "{prefix}/{type_name}/{device_id}/{message_type}",
        }
        meta = getattr(cls, "Meta", None)
        if meta:
            for key in list(defaults.keys()):
                if hasattr(meta, key):
                    defaults[key] = getattr(meta, key)
        return defaults

    @classmethod
    def get_topic(cls, device_id, message_type="data"):
        """Generate the MQTT topic for a specific device and message type."""
        from .conf import devicehub_settings

        meta = cls.get_meta()
        type_name = cls.__name__.lower()
        return meta["topic_template"].format(
            prefix=devicehub_settings.TOPIC_PREFIX,
            type_name=type_name,
            device_id=device_id,
            message_type=message_type,
        )

    @classmethod
    def get_subscribe_topics(cls):
        """Get MQTT topic patterns to subscribe to (with + wildcards)."""
        from .conf import devicehub_settings

        meta = cls.get_meta()
        type_name = cls.__name__.lower()
        prefix = devicehub_settings.TOPIC_PREFIX
        template = meta["topic_template"]
        return [
            template.format(
                prefix=prefix,
                type_name=type_name,
                device_id="+",
                message_type="data",
            ),
            template.format(
                prefix=prefix,
                type_name=type_name,
                device_id="+",
                message_type="status",
            ),
        ]

    # -- Hooks (override in subclasses) --

    def on_data(self, device, readings):
        """Called when a device sends data readings."""

    def on_status_change(self, device, old_status, new_status):
        """Called when a device status changes."""

    def on_connect(self, device):
        """Called when a device connects."""

    def on_disconnect(self, device):
        """Called when a device disconnects or heartbeat times out."""

    def on_command_response(self, device, command, response):
        """Called when a device responds to a command."""
