"""Organism-related models for lara_django_organisms.

This module contains the main Organism model and related subset models.

:authors: mark doerr <mark.doerr@uni-greifswald.de>
"""

import logging
import uuid

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

from lara_django_base.models import Tag
from lara_django_people.models import ItemSubsetAbstr

from .base import ExtraData, Trait
from .habitat import Habitat
from .taxonomy import (
    Classis,
    Cultivar,
    Domain,
    Familia,
    Genus,
    Ordo,
    Phylum,
    Regnum,
    Species,
    Subspecies,
    Variant,
)

logger = logging.getLogger(__name__)


class Organism(models.Model):
    """Organism / species / strain or cell line.

    Details should be in the organism's ontology.
    """

    model_curi = "lara:organisms/Organism"
    organism_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name_display = models.TextField(
        help_text="human readable display name of the organism, can contain spaces"
    )
    name = models.TextField(
        blank=True,
        null=True,
        help_text="organism name for referencing in code, should not contain whitespaces",
    )
    name_common = models.TextField(
        blank=True,
        null=True,
        help_text="common organism name, like 'elephant' ",
    )
    iri = models.URLField(
        blank=True,
        null=True,
        unique=True,
        max_length=512,
        help_text="International Resource Identifier - IRI: is used for semantic representation",
    )
    # PAC-ID:  https://github.com/ApiniLabs/PAC-ID
    pac_id = models.URLField(
        blank=True,
        null=True,
        unique=True,
        help_text=(
            "Publicly Addressable Content IDentifier is a globally unique identifier "
            "that operates independently of a central registry."
        ),
    )
    # taxonomy / classification
    domain = models.ForeignKey(
        Domain,
        on_delete=models.CASCADE,
        related_name="%(app_label)s_%(class)s_organism_domain_related",
        related_query_name="%(app_label)s_%(class)s_organism_domain",
        blank=True,
        null=True,
        help_text="organism's domain name",
    )
    regnum = models.ForeignKey(
        Regnum,
        on_delete=models.CASCADE,
        related_name="%(app_label)s_%(class)s_organism_regnum_related",
        related_query_name="%(app_label)s_%(class)s_organism_regnum",
        blank=True,
        null=True,
        help_text="organism's regnum name",
    )
    phylum = models.ForeignKey(
        Phylum,
        on_delete=models.CASCADE,
        related_name="%(app_label)s_%(class)s_organism_phylum_related",
        related_query_name="%(app_label)s_%(class)s_organism_phylum",
        blank=True,
        null=True,
        help_text="organism's phylum name",
    )
    classis = models.ForeignKey(
        Classis,
        on_delete=models.CASCADE,
        related_name="%(app_label)s_%(class)s_organism_classis_related",
        related_query_name="%(app_label)s_%(class)s_organism_classis",
        blank=True,
        null=True,
        help_text="organism's classis (class) name",
    )
    ordo = models.ForeignKey(
        Ordo,
        on_delete=models.CASCADE,
        related_name="%(app_label)s_%(class)s_organism_ordo_related",
        related_query_name="%(app_label)s_%(class)s_organism_ordo",
        blank=True,
        null=True,
        help_text="organism's ordo name",
    )
    familia = models.ForeignKey(
        Familia,
        on_delete=models.CASCADE,
        related_name="%(app_label)s_%(class)s_organism_familia_related",
        related_query_name="%(app_label)s_%(class)s_organism_familia",
        blank=True,
        null=True,
        help_text="organism's familia (family) name",
    )
    genus = models.ForeignKey(
        Genus,
        on_delete=models.CASCADE,
        related_name="%(app_label)s_%(class)s_organism_genus_related",
        related_query_name="%(app_label)s_%(class)s_organism_genus",
        blank=True,
        null=True,
        help_text="organism's genus name",
    )
    species = models.ForeignKey(
        Species,
        on_delete=models.CASCADE,
        related_name="%(app_label)s_%(class)s_organism_species_related",
        related_query_name="%(app_label)s_%(class)s_organism_species",
        blank=True,
        null=True,
        help_text="organism's species name",
    )
    subspecies = models.ForeignKey(
        Subspecies,
        on_delete=models.CASCADE,
        related_name="%(app_label)s_%(class)s_organism_subspecies_related",
        related_query_name="%(app_label)s_%(class)s_organism_subspecies",
        blank=True,
        null=True,
        help_text="organism's subspecies name",
    )
    variant = models.ForeignKey(
        Variant,
        on_delete=models.CASCADE,
        related_name="%(app_label)s_%(class)s_organism_variant_related",
        related_query_name="%(app_label)s_%(class)s_organism_variant",
        blank=True,
        null=True,
        help_text="organism's varian name",
    )
    cultivar = models.ForeignKey(
        Cultivar,
        on_delete=models.CASCADE,
        related_name="%(app_label)s_%(class)s_organism_cultivar_related",
        related_query_name="%(app_label)s_%(class)s_organism_cultivar",
        blank=True,
        null=True,
        help_text="organism's cultivar name",
    )
    habitats = models.ManyToManyField(
        Habitat,
        related_name="%(app_label)s_%(class)s_organism_habitat_related",
        related_query_name="%(app_label)s_%(class)s_organism_habitat",
        blank=True,
        help_text="general organism's habitats",
    )
    organisms = models.ManyToManyField(
        "self",
        related_query_name="%(app_label)s_%(class)s_organism_organisms",
        blank=True,
        help_text="organisms involved in hybrid or chimera",
    )
    traits = models.ManyToManyField(
        Trait,
        related_name="%(app_label)s_%(class)s_organism_traits_related",
        related_query_name="%(app_label)s_%(class)s_organism_traits",
        blank=True,
        help_text="general organism's traits / features / attributes",
    )
    characteristics = models.JSONField(
        blank=True,
        null=True,
        help_text="General characteristics of this variant, use 'traits' for specific traits.",
    )
    # consider a link to lara-django-sequences to store the genome as a sequence
    # efficiency needs to be tested
    genome = models.URLField(
        blank=True,
        null=True,
        max_length=512,
        help_text="URL to organism's (reference) genome",
    )

    hybrid = models.BooleanField(
        default=False,
        help_text=(
            "Hybrid - organism with mixed genetic material "
            "like mule, liger/tigon, plant hybrids."
        ),
    )
    chimera = models.BooleanField(
        default=False,
        help_text=(
            "Chimera - organisms with mixed cells from different species, "
            "like apple/pear tree."
        ),
    )

    description = models.TextField(
        null=True,
        blank=True,
        help_text="description of the organism",
    )

    data_extra = models.ManyToManyField(
        ExtraData,
        related_name="%(app_label)s_%(class)s_organism_extradata_related",
        related_query_name="%(app_label)s_%(class)s_organism_extradata",
        blank=True,
        help_text="organism related extra ",
    )
    tags = models.ManyToManyField(
        Tag,
        related_name="%(app_label)s_%(class)s_organism_tags_related",
        related_query_name="%(app_label)s_%(class)s_organism_tags",
        blank=True,
        help_text="organism related tags",
    )
    datetime_last_modified = models.DateTimeField(
        blank=True,
        null=True,
        help_text="date and time when organism data was last modified",
    )

    def save(self, *args, **kwargs):
        """Generate default values for name_display, name, and IRI."""
        if self.name_display is None or self.name_display == "":
            try:
                self.name_display = f"{self.genus.name.capitalize()} {self.species.name}"
            except Exception as e:
                logger.error(f"Error in generating name_display for organism: {e}")

        if self.name is None or self.name == "":
            if self.name_display is not None and self.name_display != "":
                self.name = self.name_display.replace(" ", "_").lower().strip()
            else:
                try:
                    self.name = f"{self.genus.name}_{self.species.name}".lower().strip()
                except Exception as e:
                    logger.error(f"Error in generating name for organism: {e}")

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

        self.datetime_last_modified = timezone.now()

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

    def __str__(self):
        """Return the name of the organism."""
        return self.name or ""

    def __repr__(self):
        """Return the name of the organism."""
        return self.name or ""

    class Meta:
        """Meta options for Organism model."""

        constraints = [
            models.UniqueConstraint(
                fields=[
                    "name",
                    "genus",
                    "species",
                    "subspecies",
                    "variant",
                    "cultivar",
                ],
                name="unique_organism",
            )
        ]


class OrganismsSubset(ItemSubsetAbstr):
    """User or group defined subset of organisms."""

    model_curi = "lara:organisms/OrganismsSubset"
    organismssubset_id = models.UUIDField(
        primary_key=True,
        default=uuid.uuid4,
        editable=False,
    )
    organisms = models.ManyToManyField(
        Organism,
        related_name="%(app_label)s_%(class)s_organisms_related",
        related_query_name="%(app_label)s_%(class)s_organisms",
        blank=True,
        help_text="organisms in this subset",
        through="OrderedOrganismsSubset",
    )


class OrderedOrganismsSubset(models.Model):
    """Through model for OrganismsSubset and Organism relationship.

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

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

    organism = models.ForeignKey(
        Organism,
        related_name="%(app_label)s_%(class)s_organism_related",
        related_query_name="%(app_label)s_%(class)s_organism",
        on_delete=models.CASCADE,
        help_text="organism in the subset",
    )

    organisms_subset = models.ForeignKey(
        OrganismsSubset,
        related_name="%(app_label)s_%(class)s_organismssubset_related",
        related_query_name="%(app_label)s_%(class)s_organismssubset",
        on_delete=models.CASCADE,
        help_text="subset of organisms",
    )

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

    class Meta:
        """Meta options for OrderedOrganismsSubset model."""

        constraints = [
            models.UniqueConstraint(
                fields=["organism", "organisms_subset"],
                name="%(app_label)s_%(class)s_organisms_organisms_subset_unique",
            ),
        ]
        ordering = ("order",)
