# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later

from datetime import timedelta

from django.db import models, transaction
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from member.models import Club
from polymorphic.models import PolymorphicModel


class QRCode(models.Model):
    """
    An QRCode model
    """
    qr_code_number = models.PositiveIntegerField(
        verbose_name=_("QR-code number"),
        unique=True,
    )

    food_container = models.ForeignKey(
        'Food',
        on_delete=models.CASCADE,
        related_name='QR_code',
        verbose_name=_('food container'),
    )

    class Meta:
        verbose_name = _("QR-code")
        verbose_name_plural = _("QR-codes")

    def __str__(self):
        return _("QR-code number {qr_code_number}").format(qr_code_number=self.qr_code_number)


class Allergen(models.Model):
    """
    A list of allergen and alimentary restrictions
    """
    name = models.CharField(
        verbose_name=_('name'),
        max_length=255,
    )

    class Meta:
        verbose_name = _('Allergen')
        verbose_name_plural = _('Allergens')

    def __str__(self):
        return self.name


class Food(PolymorphicModel):
    name = models.CharField(
        verbose_name=_('name'),
        max_length=255,
    )

    owner = models.ForeignKey(
        Club,
        on_delete=models.PROTECT,
        related_name='+',
        verbose_name=_('owner'),
    )

    allergens = models.ManyToManyField(
        Allergen,
        blank=True,
        verbose_name=_('allergen'),
    )

    expiry_date = models.DateTimeField(
        verbose_name=_('expiry date'),
        null=False,
    )

    was_eaten = models.BooleanField(
        default=False,
        verbose_name=_('was eaten'),
    )

    # is_ready != is_active : is_ready signifie que la nourriture est prête à être manger,
    #                         is_active signifie que la nourriture n'est pas encore archivé
    # il sert dans les cas où il est plus intéressant que de l'open soit conservé (confiture par ex)

    is_ready = models.BooleanField(
        default=False,
        verbose_name=_('is ready'),
    )

    is_active = models.BooleanField(
        default=True,
        verbose_name=_('is active'),
    )

    def __str__(self):
        return self.name

    @transaction.atomic
    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
        return super().save(force_insert, force_update, using, update_fields)

    class Meta:
        verbose_name = _('food')
        verbose_name = _('foods')


class BasicFood(Food):
    """
    Food which has been directly buy on supermarket
    """
    date_type = models.CharField(
        max_length=255,
        choices=(
            ("DLC", "DLC"),
            ("DDM", "DDM"),
        )
    )

    arrival_date = models.DateTimeField(
        verbose_name=_('arrival date'),
        default=timezone.now,
    )

    # label = models.ImageField(
    #     verbose_name=_('food label'),
    #     max_length=255,
    #     blank=False,
    #     null=False,
    #     upload_to='label/',
    # )

    @transaction.atomic
    def update_allergens(self):
        # update parents
        for parent in self.transformed_ingredient_inv.iterator():
            parent.update_allergens()

    @transaction.atomic
    def update_expiry_date(self):
        # update parents
        for parent in self.transformed_ingredient_inv.iterator():
            parent.update_expiry_date()

    @transaction.atomic
    def update(self):
        self.update_allergens()
        self.update_expiry_date()

    class Meta:
        verbose_name = _('Basic food')
        verbose_name_plural = _('Basic foods')


class TransformedFood(Food):
    """
    Transformed food  are a mix between basic food and meal
    """
    creation_date = models.DateTimeField(
        verbose_name=_('creation date'),
    )

    ingredient = models.ManyToManyField(
        Food,
        blank=True,
        symmetrical=False,
        related_name='transformed_ingredient_inv',
        verbose_name=_('transformed ingredient'),
    )

    # Without microbiological analyzes, the storage time is 3 days
    shelf_life = models.DurationField(
        verbose_name=_("shelf life"),
        default=timedelta(days=3),
    )

    @transaction.atomic
    def archive(self):
        # When a meal are archived, if it was eaten, update ingredient fully used for this meal
        raise NotImplementedError

    @transaction.atomic
    def update_allergens(self):
        # When allergens are changed, simply update the parents' allergens
        old_allergens = list(self.allergens.all())
        self.allergens.clear()
        for ingredient in self.ingredient.iterator():
            self.allergens.set(self.allergens.union(ingredient.allergens.all()))

        if old_allergens == list(self.allergens.all()):
            return
        super().save()

        # update parents
        for parent in self.transformed_ingredient_inv.iterator():
            parent.update_allergens()

    @transaction.atomic
    def update_expiry_date(self):
        # When expiry_date is changed, simply update the parents' expiry_date
        old_expiry_date = self.expiry_date
        self.expiry_date = self.creation_date + self.shelf_life
        for ingredient in self.ingredient.iterator():
            self.expiry_date = min(self.expiry_date, ingredient.expiry_date)

        if old_expiry_date == self.expiry_date:
            return
        super().save()

        # update parents
        for parent in self.transformed_ingredient_inv.iterator():
            parent.update_expiry_date()

    @transaction.atomic
    def update(self):
        self.update_allergens()
        self.update_expiry_date()

    @transaction.atomic
    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)

    class Meta:
        verbose_name = _('Transformed food')
        verbose_name_plural = _('Transformed foods')
