"""
Devices models for lara_django_material.

This module contains Device-related models including shape, Device,
and subset classes.
"""

import uuid

from django.conf import settings
from django.db import models
from django.utils import timezone
from lara_django_people.models import ItemSubsetAbstr

from .base import ExtraData, PartAbstr, PartDeviceClass, PartShapeAbstr
from .parts import Part


class DeviceShape(PartShapeAbstr):
    """Device shape and dimensions."""

    model_curi = "lara:material/DeviceShape"
    deviceshape_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    data_extra = models.ManyToManyField(
        ExtraData,
        related_name="%(app_label)s_%(class)s_dev_shape_data_extra_related",
        related_query_name="%(app_label)s_%(class)s_dev_shape_data_extra_related_query",
        blank=True,
        help_text="device shape extra data, like 3D model or polygon describing the device shape/dimensions",
    )

    def save(self, *args, **kwargs):
        """Generate default values for IRI."""
        if self.iri is None or self.iri == "":
            self.iri = (
                f"{settings.LARA_PREFIXES['lara']}material/DeviceShape/{self.name.replace(' ', '_').lower().strip()}"
            )

        super().save(*args, **kwargs)


class Device(PartAbstr):
    """Generic device class."""

    model_curi = "lara:material/Device"
    device_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    device_class = models.ForeignKey(
        PartDeviceClass,
        on_delete=models.CASCADE,
        blank=True,
        null=True,
        help_text="UV-spectrometer, MALDI-TOF-MS, HPLC, ..",
    )
    labware_min_capacity = models.IntegerField(
        null=True,
        blank=True,
        help_text="labware capacity, min. number of labware, some centrifuges need at least 2 labware, e.g. for balancing",
    )
    labware_max_capacity = models.IntegerField(
        null=True, blank=True, help_text="labware capacity, max. number of labware"
    )
    labware_max_height = models.FloatField(null=True, blank=True, help_text="max. labware height")
    labware_storage_layout = models.JSONField(
        null=True,
        blank=True,
        help_text="labware storage layout, like num. of stackers, num. of labware per stacker, orientation of nests, heights, ... ",
    )
    shape = models.ForeignKey(
        DeviceShape,
        on_delete=models.CASCADE,
        blank=True,
        null=True,
        help_text="shape of Device",
    )
    components = models.ManyToManyField(
        Part,
        related_query_name="%(app_label)s_%(class)s_components_related_query",
        blank=True,
        help_text="components of the device setup",
    )
    data_extra = models.ManyToManyField(
        ExtraData,
        related_name="%(app_label)s_%(class)s_devices_data_extra_related",
        related_query_name="%(app_label)s_%(class)s_devices_data_extra_related_query",
        blank=True,
        help_text="e.g. manual",
    )

    def save(self, *args, **kwargs):
        """Generate default values for type barcode and IRI."""
        if self.name is None or self.name == "":
            if self.name_display is not None or self.name_display != "":
                self.name = self.name_display.replace(" ", "_").lower().strip()

        if self.name is not None or self.name != "":
            self.name = self.name.replace(" ", "_").lower().strip()

        if self.name_display is None or self.name_display == "":
            self.name_display = self.name

        if self.type_barcode == "" or self.type_barcode is None:
            self.type_barcode = f"AUTO_{str(uuid.uuid4())[:8]}"  # avoiding unique constraint violation

        if self.iri is None or self.iri == "":
            self.iri = f"{settings.LARA_PREFIXES['lara']}material/Device/{self.name.replace(' ', '_').lower().strip()}_{str(self.device_id)[:8]}"

        if self.datetime_last_modified is None:
            self.datetime_last_modified = timezone.now()

        super().save(*args, **kwargs)


class DevicesSubset(ItemSubsetAbstr):
    """
    Devices subset.

    It can be used for user or group defined subsets of devices.
    """

    model_curi = "lara:material/DevicesSubset"
    devicessubset_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    devices = models.ManyToManyField(
        Device,
        related_name="%(app_label)s_%(class)s_devices_subset_related",
        related_query_name="%(app_label)s_%(class)s_devices_subset_related_query",
        blank=True,
        help_text="devices in the subset",
        through="OrderedDevicesSubset",
    )


class OrderedDevicesSubset(models.Model):
    """
    Through model for DevicesSubset and Device relationship.

    This class can be used to store the order of devices in a subset.
    """

    model_curi = "lara:material/OrderedDevicesSubset"

    ordereddevicessubset_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

    device = models.ForeignKey(
        Device,
        related_name="%(app_label)s_%(class)s_device_related",
        related_query_name="%(app_label)s_%(class)s_device",
        on_delete=models.CASCADE,
        help_text="device in the subset",
    )

    devices_subset = models.ForeignKey(
        DevicesSubset,
        related_name="%(app_label)s_%(class)s_devices_subset_related",
        related_query_name="%(app_label)s_%(class)s_devices_subset",
        on_delete=models.CASCADE,
        help_text="subset of devices",
    )

    order = models.IntegerField(help_text="order of device in subset")

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["device", "devices_subset"],
                name="%(app_label)s_%(class)s_device_devices_subset_unique",
            ),
        ]
        ordering = ("order",)
