"""_____________________________________________________________________

:PROJECT: LARAsuite

*lara_django_processes models for procedure and procedure instance*

: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 datetime import datetime

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

from lara_django_people.models import ItemSubsetAbstr
from .abstracts import ProcessAbstr, ProcMethodClass, ProcessInstanceAbstr
from .method import Method


class Procedure(ProcessAbstr):
    """
    A procedure is a (defined) sequence of actions leading to a certain result,
    e.g. the sequence of gradient steps in an HPLC measurement,
         a sequence of transfer steps on a liquid handler, an enzyme assay ...
    This is a generic Procedure representation, definition of steps etc., for a particular run of this procedure, see MethodInstance
    """

    model_curi = "lara:processes/Procedure"
    procedure_id = models.UUIDField(
        primary_key=True, default=uuid.uuid4, editable=False
    )

    procedure_class = models.ForeignKey(
        ProcMethodClass,
        related_name="%(app_label)s_%(class)s_procedure_classes_related",
        related_query_name="%(app_label)s_%(class)s_procedure_classes_related_query",
        on_delete=models.CASCADE,
        blank=True,
        null=True,
        help_text="class of the procedure",
    )
    procedure_txt = models.TextField(
        blank=True,
        null=True,
        help_text="Procedure text representation, e.g. SOP, python code, R-script, ...",
    )
    procedure_json = models.JSONField(
        blank=True, null=True, help_text="Procedure JSON representation"
    )

    procedure_file = models.FileField(
        upload_to="lara_processes/procedures/",
        blank=True,
        null=True,
        help_text="rel. path/filename to procedures",
    )

    method = models.ForeignKey(
        Method,
        related_name="%(app_label)s_%(class)s_method_related",
        related_query_name="%(app_label)s_%(class)s_method_related",
        on_delete=models.CASCADE,
        blank=True,
        null=True,
        help_text="Reference to a method if this process is described by a method."
    )

    def save(self, *args, **kwargs):
        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/Procedure/{self.name}"
                )
            except IntegrityError as err:
                self.iri = f"{settings.LARA_PREFIXES['lara']}processes/Procedure/{self.name}/{str(self.procedure_id)[:8]}"

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

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


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

    model_curi = "lara:processes/ProceduresSubset"
    proceduressubset_id = models.UUIDField(
        primary_key=True, default=uuid.uuid4, editable=False
    )
    procedures = models.ManyToManyField(
        Procedure,
        related_name="%(app_label)s_%(class)s_procedures_related",
        related_query_name="%(app_label)s_%(class)s_procedures_related_query",
        blank=True,
        help_text="all procedures of a subset",
        through="OrderedProceduresSubset",
    )

    # class Meta:
    #


class OrderedProceduresSubset(models.Model):
    """Through model for the many-to-many relationship between ProceduresSubset and Procedure.
    This class can be used to store the order of procedures in a subset"""

    model_curi = "lara:processes/OrderedProceduresSubset"

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

    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 subset",
    )

    procedures_subset = models.ForeignKey(
        ProceduresSubset,
        related_name="%(app_label)s_%(class)s_proceduressubset_related",
        related_query_name="%(app_label)s_%(class)s_proceduressubset",
        on_delete=models.CASCADE,
        help_text="subset of procedures",
    )

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

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["procedure", "procedures_subset"],
                name="%(app_label)s_%(class)s_procedure_proceduressubset_unique",
            ),
        ]
        ordering = ("order",)


class ProcedureInstance(ProcessInstanceAbstr):
    """
    A procedure is a (defined) sequence of actions leading to a certain result,
    e.g. the sequence of gradient steps in an HPLC measurement,
         a sequence of transfer steps on a liquid handler, an enzyme assay ...
    """

    model_curi = "lara:processes/ProcedureInstance"
    procedureinstance_id = models.UUIDField(
        primary_key=True, default=uuid.uuid4, editable=False
    )
    procedure_base = models.ForeignKey(
        Procedure,
        related_name="%(app_label)s_%(class)s_procesure_ref_related",
        related_query_name="%(app_label)s_%(class)s_procedure_base_related_query",
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        help_text="reference to abstract procedure",
    )
    procedure_json = models.JSONField(
        blank=True, null=True, help_text="Procedure JSON representation"
    )
    procedure_file = models.FileField(
        upload_to="lara_processes/procedure_inst/",
        blank=True,
        null=True,
        help_text="rel. path/filename to procedures",
    )

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

        # iterate over all fields of the method_base and copy them to the method_instance, but only, if they are not set
        try:
            for field in self.procedure_base._meta.fields:
                if getattr(self, field.name) is None:
                    setattr(self, field.name, getattr(self.method_base, field.name))
        except AttributeError as e:
            pass  # missing field in procedure_base, e.g. procedure_id

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

        if (self.name is None or self.name == "") and self.name_display is not None:
            self.name = (
                self.name_display.strip().replace(" ", "_").lower() + f"_{self.version}"
            )
        if self.name is None or self.name == "":
            self.name = (
                f"_{datetime.now().strftime('%Y%m%d')}" + self.procedure_base.name
            )

        if self.procedure_base is not None:
            if self.namespace is None or self.namespace == "":
                self.namespace = self.procedure_base.namespace

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

            if self.description is None or self.description == "":
                self.description = self.procedure_base.description

            if self.procedure_json is None or self.procedure_json == "":
                self.procedure_json = self.procedure_base.procedure_json
        elif self.name is None or self.name == "":
            self.name = f"{datetime.now().strftime('%Y%m%d')}_procedure"

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

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

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


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

    model_curi = "lara:processes/ProcedureInstancesSubset"
    procedureinstancessubset_id = models.UUIDField(
        primary_key=True, default=uuid.uuid4, editable=False
    )
    procedure_instances = models.ManyToManyField(
        ProcedureInstance,
        related_name="%(app_label)s_%(class)s_procedure_instances_related",
        related_query_name="%(app_label)s_%(class)s_procedure_instances_related_query",
        blank=True,
        help_text="all procedure instances of a subset",
        through="OrderedProcedureInstancesSubset",
    )


class OrderedProcedureInstancesSubset(models.Model):
    """Through model for the many-to-many relationship between ProcedureInstancesSubset and ProcedureInstance.
    This class can be used to store the order of procedure instances in a subset"""

    model_curi = "lara:processes/OrderedProcedureInstancesSubset"

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

    procedure_instance = models.ForeignKey(
        ProcedureInstance,
        related_name="%(app_label)s_%(class)s_procedure_instance_related",
        related_query_name="%(app_label)s_%(class)s_procedure_instance",
        on_delete=models.CASCADE,
        help_text="procedure instance in the subset",
    )

    procedure_instances_subset = models.ForeignKey(
        ProcedureInstancesSubset,
        related_name="%(app_label)s_%(class)s_procedureinstancessubset_related",
        related_query_name="%(app_label)s_%(class)s_procedureinstancessubset",
        on_delete=models.CASCADE,
        help_text="subset of procedure instances",
    )

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

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["procedure_instance", "procedure_instances_subset"],
                name="%(app_label)s_%(class)s_procedure_instance_procedureinstancessubset_unique",
            ),
        ]
        ordering = ("order",)

