"""_____________________________________________________________________

:PROJECT: LARAsuite

*lara_django_processes models for process*

:details: lara_django_processes database models.

:authors: - mark doerr <mark.doerr@uni-greifswald.de>
          - marco di sarno <disarno@k3b.de>

.. note:: -
.. todo:: - remove unwanted models/fields
________________________________________________________________________
"""

import uuid

from django.conf import settings
from django.db import IntegrityError
from django.db import models
from django.db.models import Q
from django.utils import timezone

from lara_django_people.models import ItemSubsetAbstr
from .abstracts import ProcessAbstr, ProcMethodClass


class Process(ProcessAbstr):
    """
    A process is a combination of various procedures and decisions,
    e.g. a robot process, a fermentation process, ....
    """

    model_curi = "lara:processes/Process"
    process_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    process_class = models.ForeignKey(
        ProcMethodClass,
        related_name="%(app_label)s_%(class)s_process_classes_related",
        related_query_name="%(app_label)s_%(class)s_processes_class_related_query",
        on_delete=models.CASCADE,
        blank=True,
        null=True,
        help_text="class of the process",
    )
    procedures = models.ManyToManyField(
        "Procedure",
        related_name="procedures_processes",
        blank=True,
        help_text="all procedures of a process",
        # through="OrderedProcedures", # TODO: to be discussed
    )
    process_json = models.JSONField(
        blank=True, null=True, help_text="Process JSON representation"
    )
    process_file = models.FileField(
        upload_to="lara_processes/processes/",
        blank=True,
        null=True,
        help_text="rel. path/filename to processes",
    )
    graph_representation = models.JSONField(
        blank=True,
        null=True,
        help_text="Workflow graph representation of the process.",
    )

    class Meta:
        verbose_name_plural = "Processes"

    def save(self, *args, **kwargs):
        # ~ if not self.slug :
        # ~ self.slug = '-'.join((self.proc_class, self.name, self.version)).lower()

        if self.version is None or self.version == "":
            self.version = "0.0.1"

        if self.uri == "":
            self.uri = None

        if self.name is None or self.name == "":
            self.name = (
                self.name_display.strip().replace(" ", "_").lower() + f"_{self.version}"
            )

        if self.iri is None or self.iri == "":
            try:
                self.iri = (
                    f"{settings.LARA_PREFIXES['lara']}processes/Process/{self.name}"
                )
            except IntegrityError as err:
                self.iri = f"{settings.LARA_PREFIXES['lara']}processes/Process/{self.name}/{str(self.process_id)[:8]}"

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

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


class ProcessStepClass(models.Model):
    """Class / Type of step in a procedure (instance), e.g., operation, move, transfer, decision, computation ..."""

    model_curi = "lara:processes/ProcessStepClass"
    processstepclass_id = models.UUIDField(
        primary_key=True, default=uuid.uuid4, editable=False
    )
    name = models.TextField(
        unique=True,
        null=True,
        help_text="name of step class, e.g. move, transfer, decision, computation ...",
    )
    iri = models.TextField(
        blank=True,
        null=True,
        unique=True,
        max_length=512,
        help_text="International Resource Identifier - IRI: is used for semantic representation ",
    )
    processstep_schema = models.JSONField(
        blank=True,
        help_text="JSON schema for the process step, e.g. for validation of parameters",
    )
    description = models.TextField(
        blank=True, null=True, help_text="description of step class"
    )

    class Meta:
        verbose_name_plural = "ProcessStepClasses"

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

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

    def save(self, *args, **kwargs):
        """
        Here we generate some default values for IRIs etc.
        """

        if self.iri is None or self.iri == "":
            self.iri = f"{settings.LARA_PREFIXES['lara']}processes/ProcessStepClass/{self.name}"

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


class ProcessStep(models.Model):
    """
    Through model for the many-to-many relationship between Process and Procedure.

    Each process step should have a procedure defined.
    """

    model_curi = "lara:processes/ProcessStep"

    orderedprocedures_id = models.UUIDField(
        primary_key=True, default=uuid.uuid4, editable=False
    )
    name = models.TextField(blank=True, help_text="name of the step")

    step_class = models.ForeignKey(
        ProcessStepClass,
        related_name="%(app_label)s_%(class)s_step_classes_related",
        related_query_name="%(app_label)s_%(class)s_step_classes_related_query",
        on_delete=models.CASCADE,
        blank=True,
        null=True,
    )

    parent_process = models.ForeignKey(
        Process,
        related_name="%(app_label)s_%(class)s_parent_process_related",
        related_query_name="%(app_label)s_%(class)s_parent_process",
        on_delete=models.CASCADE,
        help_text="process",
    )

    procedure = models.ForeignKey(
        "Procedure",
        related_name="%(app_label)s_%(class)s_procedure_related",
        related_query_name="%(app_label)s_%(class)s_procedure",
        on_delete=models.CASCADE,
        help_text="procedure in the process, only one procedure or subprocess may be specified",
    )

    subprocess = models.ForeignKey(
        Process,
        related_name="%(app_label)s_%(class)s_subprocess_related",
        related_query_name="%(app_label)s_%(class)s_subprocess",
        on_delete=models.CASCADE,
        help_text="subprocess step in the process, only one procedure or subprocess may be specified",
    )

    graph_reference = models.UUIDField(help_text="UUID of this process step in process workflow graph")

    parameters = models.JSONField(
        blank=True, null=True, help_text="process step parameters"
    )

    class Meta:
        """Meta options for the ProcessStep model."""

        constraints = (
            models.UniqueConstraint(
                fields=["procedure", "graph_reference"],
                name="%(app_label)s_%(class)s_process_graph_ref_unique",
            ),
            models.UniqueConstraint(
                fields=["parent_process", "name"],
                name="%(app_label)s_%(class)s_process_name_unique",
            ),
            models.CheckConstraint(
                condition=Q(subprocess__isnull=True) | Q(procedure__isnull=True),
                name="%(app_label)s_%(class)s_only_subprocess_or_procedure",
            )
        )


class ProcessesSubset(ItemSubsetAbstr):
    """
    A subset of processes, e.g. all processes for a certain lab, or all processes for a certain project, ...
    """

    model_curi = "lara:processes/ProcessesSubset"
    processessubset_id = models.UUIDField(
        primary_key=True, default=uuid.uuid4, editable=False
    )
    processes = models.ManyToManyField(
        Process,
        related_name="%(app_label)s_%(class)s_processes_related",
        related_query_name="%(app_label)s_%(class)s_processes_related_query",
        blank=True,
        help_text="all processes of a subset",
        through="OrderedProcessesSubset",
    )
# TODO: replace by generic decorator
# @with_through_model_fields({
#     'processes': ProcessSubset,
# })

class OrderedProcessesSubset(models.Model):
    """Through model for the many-to-many relationship between ProcessesSubset and Process.
    This class can be used to store the order of processes in a subset"""

    model_curi = "lara:processes/OrderedProcessesSubset"

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

    process = models.ForeignKey(
        Process,
        related_name="%(app_label)s_%(class)s_process_related",
        related_query_name="%(app_label)s_%(class)s_process",
        on_delete=models.CASCADE,
        help_text="process in the subset",
    )

    processes_subset = models.ForeignKey(
        ProcessesSubset,
        related_name="%(app_label)s_%(class)s_processessubset_related",
        related_query_name="%(app_label)s_%(class)s_processessubset",
        on_delete=models.CASCADE,
        help_text="subset of processes",
    )

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

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["process", "processes_subset"],
                name="%(app_label)s_%(class)s_process_processessubset_unique",
            ),
        ]
        ordering = ("order",)
