from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models


class ReadingType:
    """Supported data types for Reading fields."""

    FLOAT = "float"
    INTEGER = "integer"
    BOOLEAN = "boolean"
    STRING = "string"
    JSON = "json"
    BINARY = "binary"


reading_types = ReadingType()


class Reading:
    """Declares a sensor reading field on a DeviceType.

    Each Reading becomes a nullable column on the generated Reading model,
    forming a wide time-series table (one row = one data point, multiple readings).

    Usage:
        class WeatherStation(DeviceType):
            temperature = Reading(type=reading_types.FLOAT, unit="C", range=(-40, 80))
            humidity = Reading(type=reading_types.FLOAT, unit="%", range=(0, 100))
    """

    _FIELD_MAP = {
        ReadingType.FLOAT: (models.FloatField, {}),
        ReadingType.INTEGER: (models.IntegerField, {}),
        ReadingType.BOOLEAN: (models.BooleanField, {}),
        ReadingType.STRING: (models.CharField, {"max_length": 255}),
        ReadingType.JSON: (models.JSONField, {}),
        ReadingType.BINARY: (models.BinaryField, {}),
    }

    def __init__(
        self,
        type=ReadingType.FLOAT,
        unit="",
        range=None,
        required=False,
        verbose_name=None,
    ):
        self.type = type
        self.unit = unit
        self.range = range
        self.required = required
        self.verbose_name = verbose_name
        self.name = None  # Set by DeviceTypeMeta

    def to_django_field(self):
        """Convert this Reading to a concrete Django model field."""
        field_class, extra_kwargs = self._FIELD_MAP.get(
            self.type, (models.CharField, {"max_length": 255})
        )
        kwargs = {
            "null": True,
            "blank": True,
            **extra_kwargs,
        }
        if self.unit:
            kwargs["help_text"] = self.unit
        if self.verbose_name:
            kwargs["verbose_name"] = self.verbose_name

        field = field_class(**kwargs)

        # Range validators
        if self.range and len(self.range) == 2:
            if self.range[0] is not None:
                field.validators.append(MinValueValidator(self.range[0]))
            if self.range[1] is not None:
                field.validators.append(MaxValueValidator(self.range[1]))

        return field

    def __repr__(self):
        return f"Reading(type={self.type!r}, unit={self.unit!r})"


class Command:
    """Declares a command that can be sent to a device.

    Usage:
        class WeatherStation(DeviceType):
            reboot = Command()
            set_interval = Command(payload={"interval": int})
    """

    def __init__(self, payload=None, description="", qos=1):
        self.payload = payload or {}
        self.description = description
        self.qos = qos
        self.name = None  # Set by DeviceTypeMeta

    def validate_payload(self, data):
        """Validate command payload against declared types."""
        for key, expected_type in self.payload.items():
            if key not in data:
                raise ValueError(f"Missing required payload field: {key}")
            if not isinstance(data[key], expected_type):
                raise TypeError(
                    f"Field {key}: expected {expected_type.__name__}, "
                    f"got {type(data[key]).__name__}"
                )
        return True

    def __repr__(self):
        return f"Command(payload={self.payload!r})"


class StateField:
    """Declares a persistent state field on a device (beyond base fields).

    State fields become columns on the generated Device model.

    Usage:
        class SmartCamera(DeviceType):
            recording_mode = StateField(
                type="string",
                default="continuous",
                choices=["continuous", "motion", "scheduled"],
            )
    """

    _FIELD_MAP = {
        "string": (models.CharField, {"max_length": 255}),
        "float": (models.FloatField, {}),
        "integer": (models.IntegerField, {}),
        "boolean": (models.BooleanField, {}),
        "json": (models.JSONField, {}),
        "datetime": (models.DateTimeField, {}),
    }

    def __init__(self, type="string", default=None, choices=None, verbose_name=None):
        self.type = type
        self.default = default
        self.choices = choices
        self.verbose_name = verbose_name
        self.name = None  # Set by DeviceTypeMeta

    def to_django_field(self):
        """Convert this StateField to a concrete Django model field."""
        field_class, extra_kwargs = self._FIELD_MAP.get(
            self.type, (models.CharField, {"max_length": 255})
        )
        kwargs = {
            "null": True,
            "blank": True,
            **extra_kwargs,
        }
        if self.default is not None:
            kwargs["default"] = self.default
            kwargs["null"] = False

        if self.choices:
            kwargs["choices"] = [(c, c) for c in self.choices]

        if self.verbose_name:
            kwargs["verbose_name"] = self.verbose_name

        return field_class(**kwargs)

    def __repr__(self):
        return f"StateField(type={self.type!r})"
