import re

from django.db import models

from ..models import BaseDevice, BaseReading, BaseMessage
from ..registry import registry


def _camel_to_words(name):
    """Convert CamelCase to 'Camel Case'."""
    s1 = re.sub(r"(.)([A-Z][a-z]+)", r"\1 \2", name)
    return re.sub(r"([a-z0-9])([A-Z])", r"\1 \2", s1)


def create_models(device_type_cls, calling_module):
    """Generate concrete Django models from a DeviceType class.

    Creates three models:
      - {Type}Device  (extends BaseDevice + StateFields)
      - {Type}Reading (extends BaseReading + Reading columns)
      - {Type}Message (extends BaseMessage)

    All models get __module__ set to calling_module so Django's migration
    system discovers them in the correct app.
    """
    type_name = device_type_cls.__name__
    words = _camel_to_words(type_name)

    device_model = _create_device_model(
        device_type_cls, type_name, words, calling_module
    )
    reading_model = _create_reading_model(
        device_type_cls, type_name, words, device_model, calling_module
    )
    message_model = _create_message_model(
        device_type_cls, type_name, words, device_model, calling_module
    )

    # Store references back on the DeviceType
    device_type_cls._device_model = device_model
    device_type_cls._reading_model = reading_model
    device_type_cls._message_model = message_model

    # Register models in the global registry
    registry.register_models(
        device_type_cls, device_model, reading_model, message_model
    )

    return device_model, reading_model, message_model


def _create_device_model(device_type_cls, type_name, words, calling_module):
    model_name = f"{type_name}Device"

    attrs = {
        "__module__": calling_module,
        "__qualname__": model_name,
        "Meta": type(
            "Meta",
            (),
            {
                "verbose_name": words,
                "verbose_name_plural": f"{words}s",
            },
        ),
        # Back-reference to the DeviceType class
        "_device_type_cls": device_type_cls,
    }

    # Add StateField columns
    for name, field in device_type_cls._state_fields.items():
        attrs[name] = field.to_django_field()

    # type() triggers ModelBase.__new__, fully registering with Django
    return type(model_name, (BaseDevice,), attrs)


def _create_reading_model(
    device_type_cls, type_name, words, device_model, calling_module
):
    model_name = f"{type_name}Reading"

    attrs = {
        "__module__": calling_module,
        "__qualname__": model_name,
        "device": models.ForeignKey(
            device_model,
            on_delete=models.CASCADE,
            related_name="readings",
        ),
        "Meta": type(
            "Meta",
            (),
            {
                "verbose_name": f"{words} Reading",
                "verbose_name_plural": f"{words} Readings",
                "indexes": [
                    models.Index(fields=["device", "timestamp"]),
                ],
            },
        ),
    }

    # Add a nullable column for each Reading field
    for name, reading in device_type_cls._readings.items():
        attrs[name] = reading.to_django_field()

    return type(model_name, (BaseReading,), attrs)


def _create_message_model(
    device_type_cls, type_name, words, device_model, calling_module
):
    model_name = f"{type_name}Message"

    attrs = {
        "__module__": calling_module,
        "__qualname__": model_name,
        "device": models.ForeignKey(
            device_model,
            on_delete=models.CASCADE,
            related_name="messages",
        ),
        "Meta": type(
            "Meta",
            (),
            {
                "verbose_name": f"{words} Message",
                "verbose_name_plural": f"{words} Messages",
            },
        ),
    }

    return type(model_name, (BaseMessage,), attrs)
