"""
Labware models for lara_django_material.

This module contains Labware-related models including LabwareClass, WellShape,
LabwareShape, Labware, 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, PartShapeAbstr
from .parts import Part


class LabwareClass(models.Model):
    """
    Labware classes.

    Examples: microtiterplate / MTP, tube, flask, ...
    """

    model_curi = "lara:material/LabwareClass"
    labwareclass_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.TextField(unique=True)
    iri = models.URLField(
        blank=True,
        null=True,
        unique=True,
        help_text="International Resource Identifier - IRI: is used for semantic representation ",
    )
    description = models.TextField(
        default="no description",
        null=True,
        blank=True,
        help_text="description of the labware class",
    )

    class Meta:
        verbose_name_plural = "LabwareClasses"
        # db_table = "lara_labwares_labware_class"

    def __str__(self):
        return (self.name) or ""

    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/LabwareClass/{self.name.replace(' ', '_').lower().strip()}"
            )
        super().save(*args, **kwargs)


class WellShape(models.Model):
    """
    Labware Well Shape.

    Describes shapes of wells: round, square, buffeled, flowered, ...
    This can also be used to describe the bottom shape, like flat, conical.
    """

    model_curi = "lara:material/WellShape"
    wellshape_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.TextField(help_text="Well shape name")
    uri = models.URLField(
        blank=True,
        null=True,
        help_text="Universal Resource Indentifier - URI - can be used to identify the shape ",
    )
    iri = models.URLField(
        blank=True,
        null=True,
        unique=True,
        help_text="International Resource Identifier - IRI: is used for semantic representation ",
    )
    depth_well = models.FloatField(blank=True, null=True, help_text="total well depth=hight")
    shape_well = models.TextField(
        unique=True,
        null=True,
        help_text="labware well shape,e.g. round, square, buffeled,...",
    )
    shape_bottom = models.TextField(unique=True, null=True, help_text="well, bottom shape, flat, round, conical")
    top_radius_xy = models.FloatField(blank=True, null=True, help_text="radius of a round well at the top opening")
    bottom_radius_xy = models.FloatField(blank=True, null=True, help_text="radius of a round bottom in xy direction")
    bottom_radius_z = models.FloatField(
        blank=True,
        null=True,
        help_text="radius of a round bottom in z (hight) direction",
    )
    cone_angle = models.FloatField(blank=True, null=True, help_text="opening angle of cone in deg")
    cone_depth = models.FloatField(blank=True, null=True, help_text="opening angle of cone in deg")
    shape_polygon_xy = models.TextField(
        null=True,
        help_text="generalized shape polygon for more complex well shapes, in xy direction",
    )
    shape_polygon_z = models.TextField(
        null=True,
        help_text="generalized shape polygon for more complex well shapes, in z direction",
    )
    model_2d = models.TextField(blank=True, null=True, help_text="2D model of well shape in, e.g., SVG format")
    model_3d = models.TextField(blank=True, null=True, help_text="3D model of well shape in, e.g., STL format")
    model_json = models.JSONField(blank=True, null=True, help_text="2D or 3D model of Well shape")
    data_extra = models.ManyToManyField(
        ExtraData,
        related_name="%(app_label)s_%(class)s_data_extra_related",
        related_query_name="%(app_label)s_%(class)s_data_extra_related_query",
        blank=True,
        help_text="well shape extra data, like 3D model or polygon describing the well shape/dimensions",
    )
    description = models.TextField(blank=True, null=True, help_text="description of labware shape")

    def __str__(self):
        return self.name or ""

    def __repr__(self):
        return self.name or ""

    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/WellShape/{self.name.replace(' ', '_').lower().strip()}"
            )

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


class LabwareShape(PartShapeAbstr):
    """
    Labware and Lid shape and dimensions.

    E.g., special characteristics of a labware, like material, shape,
    well-shape ...
    """

    model_curi = "lara:material/LabwareShape"
    labwareshape_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    well_dist_row = models.FloatField(blank=True, null=True, help_text="well-to-well distance in row direction")
    well_dist_col = models.FloatField(blank=True, null=True, help_text="well-to-well distance in column direction")
    well_shape = models.ForeignKey(
        WellShape,
        related_name="%(app_label)s_%(class)s_well_shape_related",
        related_query_name="%(app_label)s_%(class)s_well_shape_related_query",
        null=True,
        blank=True,
        on_delete=models.CASCADE,
        help_text="Well shape description",
    )
    data_extra = models.ManyToManyField(
        ExtraData,
        related_name="%(app_label)s_%(class)s_lws_data_extra_related",
        related_query_name="%(app_label)s_%(class)s_lws_data_extra_related_query",
        blank=True,
        help_text="labware shape extra data, like 3D model or polygon describing the labware shape/dimensions",
    )
    description = models.TextField(blank=True, null=True, help_text="description of labware shape")

    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/LabwareShape/{self.name.replace(' ', '_').lower().strip()}"
            )
        super().save(*args, **kwargs)


class Labware(PartAbstr):
    """
    Generic labware class.

    Models/describes the general properties of a labware like geometry, volume,
    manufacturer. All concrete instances shall appear in the labware store.
    """

    model_curi = "lara:material/Labware"
    labware_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    labware_class = models.ForeignKey(
        LabwareClass,
        on_delete=models.CASCADE,
        blank=True,
        null=True,
        help_text="class of the labware, e.g. MTP-SBS/SBS, vial/HPLC,  ...",
    )
    num_rows = models.IntegerField(null=True, blank=True, help_text="number of rows of labware")
    num_cols = models.IntegerField(null=True, blank=True, help_text="number of columns of labware")
    num_wells = models.IntegerField(null=True, blank=True, help_text="number of wells - could be auto generated")
    height = models.FloatField(null=True, blank=True, help_text="height of labware in mm")
    shape = models.ForeignKey(
        LabwareShape,
        related_name="%(app_label)s_%(class)s_labware_shape_related",
        related_query_name="%(app_label)s_%(class)s_labware_shape_related_query",
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        help_text="shape of labware",
    )
    components = models.ManyToManyField(
        Part,
        related_query_name="%(app_label)s_%(class)s_components_related_query",
        blank=True,
        help_text="components of the labware",
    )
    # material # polymer, properties, like solvent tolerance, transparency, ....

    # lid info
    lids = models.ManyToManyField("self", blank=True, help_text="reference to possible lids")
    liddable = models.BooleanField(null=True, blank=True, help_text="labware is liddable")

    # seal info
    sealable = models.BooleanField(default=False, null=True, help_text="labware is sealable")
    data_extra = models.ManyToManyField(
        ExtraData,
        related_name="%(app_label)s_%(class)s_labware_data_extra_related",
        related_query_name="%(app_label)s_%(class)s_labware_data_extra_related_query",
        blank=True,
        help_text="e.g. manual",
    )

    def save(self, *args, **kwargs):
        """Generate default values for iri / type_barcode."""
        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/Labware/{self.name.replace(' ', '_').lower().strip()}_{str(self.labware_id)[:8]}"

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

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


class LabwareSubset(ItemSubsetAbstr):
    """
    Labware subset.

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

    model_curi = "lara:material/LabwareSubset"
    labwaresubset_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    labware = models.ManyToManyField(
        Labware,
        related_name="%(app_label)s_%(class)s_labware_setup_related",
        related_query_name="%(app_label)s_%(class)s_labware_setup_related_query",
        blank=True,
        help_text="labwares in the subset",
        through="OrderedLabwareSubset",
    )


class OrderedLabwareSubset(models.Model):
    """
    Through model for LabwareSubset and Labware relationship.

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

    model_curi = "lara:material/OrderedLabwareSubset"

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

    labware = models.ForeignKey(
        Labware,
        related_name="%(app_label)s_%(class)s_labware_related",
        related_query_name="%(app_label)s_%(class)s_labware",
        on_delete=models.CASCADE,
        help_text="labware in the subset",
    )

    labwares_subset = models.ForeignKey(
        LabwareSubset,
        related_name="%(app_label)s_%(class)s_labwares_subset_related",
        related_query_name="%(app_label)s_%(class)s_labwares_subset",
        on_delete=models.CASCADE,
        help_text="subset of labwares",
    )

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

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["labware", "labwares_subset"],
                name="%(app_label)s_%(class)s_labware_labwares_subset_unique",
            ),
        ]
        ordering = ("order",)
