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

from django.db import models, transaction
from django.utils import timezone
from django.contrib.auth.models import User
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _


class Family(models.Model):
    name = models.CharField(
        max_length=255,
        verbose_name=_('name'),
        unique=True,
    )

    description = models.CharField(
        max_length=255,
        verbose_name=_('description'),
    )

    score = models.PositiveIntegerField(
        verbose_name=_('score'),
        default=0,
    )

    rank = models.PositiveIntegerField(
        verbose_name=_('rank'),
    )

    display_image = models.ImageField(
        verbose_name=_('display image'),
        max_length=255,
        blank=False,
        null=False,
        upload_to='pic/',
        default='pic/default.png'
    )

    class Meta:
        verbose_name = _('Family')
        verbose_name_plural = _('Families')

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse_lazy('family:family_detail', args=(self.pk,))

    def update_score(self, *args, **kwargs):
        challenge_set = Challenge.objects.select_for_update().filter(achievement__family=self, achievement__valid=True)
        points_sum = challenge_set.aggregate(models.Sum("points"))
        self.score = points_sum["points__sum"] if points_sum["points__sum"] else 0
        self.save()
        self.update_ranking()

    @staticmethod
    def update_ranking(*args, **kwargs):
        """
        Update ranking when adding or removing points
        """
        family_set = Family.objects.select_for_update().all().order_by("-score")
        for i in range(family_set.count()):
            if i == 0 or family_set[i].score != family_set[i - 1].score:
                new_rank = i + 1
            family = family_set[i]
            family.rank = new_rank
            family._force_save = True
            family.save()

    def save(self, *args, **kwargs):
        if self.rank is None:
            last_family = Family.objects.order_by("rank").last()
            if last_family is None or last_family.score > self.score:
                self.rank = Family.objects.count() + 1
            else:
                self.rank = last_family.rank
        super().save(*args, **kwargs)


class FamilyMembership(models.Model):
    user = models.OneToOneField(
        User,
        on_delete=models.PROTECT,
        related_name=_('family_memberships'),
        verbose_name=_('user'),
    )

    family = models.ForeignKey(
        Family,
        on_delete=models.PROTECT,
        related_name=_('memberships'),
        verbose_name=_('family'),
    )

    year = models.PositiveIntegerField(
        verbose_name=_('year'),
        default=timezone.now().year,
    )

    class Meta:
        unique_together = ('user', 'year',)
        verbose_name = _('family membership')
        verbose_name_plural = _('family memberships')

    def __str__(self):
        return _('Family membership of {user} to {family}').format(user=self.user.username, family=self.family.name, )


class Challenge(models.Model):
    name = models.CharField(
        max_length=255,
        verbose_name=_('name'),
    )

    description = models.CharField(
        max_length=255,
        verbose_name=_('description'),
    )

    points = models.PositiveIntegerField(
        verbose_name=_('points'),
    )

    @property
    def obtained(self):
        achievements = Achievement.objects.filter(challenge=self, valid=True)
        return achievements.count()

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse_lazy('family:challenge_detail', args=(self.pk,))

    @transaction.atomic
    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        # Update families who already obtained this challenge
        achievements = Achievement.objects.filter(challenge=self)
        for achievement in achievements:
            achievement.save()

    class Meta:
        verbose_name = _('challenge')
        verbose_name_plural = _('challenges')


class Achievement(models.Model):
    challenge = models.ForeignKey(
        Challenge,
        on_delete=models.PROTECT,

    )
    family = models.ForeignKey(
        Family,
        on_delete=models.PROTECT,
        verbose_name=_('family'),
    )

    obtained_at = models.DateTimeField(
        verbose_name=_('obtained at'),
        default=timezone.now,
    )

    valid = models.BooleanField(
        verbose_name=_('valid'),
        default=False,
    )

    class Meta:
        unique_together = ('challenge', 'family',)
        verbose_name = _('achievement')
        verbose_name_plural = _('achievements')

    def __str__(self):
        return _('Challenge {challenge} carried out by Family {family}').format(challenge=self.challenge.name, family=self.family.name, )

    @transaction.atomic
    def save(self, *args, update_score=True, **kwargs):
        """
        When saving, also grants points to the family
        """
        self.family = Family.objects.select_for_update().get(pk=self.family_id)
        self.challenge = Challenge.objects.select_for_update().get(pk=self.challenge_id)

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

        if update_score:
            self.family.refresh_from_db()
            self.family.update_score()

    @transaction.atomic
    def delete(self, *args, **kwargs):
        """
        When deleting, also removes points from the family
        """
        # Get the family and challenge before deletion
        self.family = Family.objects.select_for_update().get(pk=self.family_id)

        # Delete the achievement
        super().delete(*args, **kwargs)

        # Remove points from the family
        self.family.refresh_from_db()
        self.family.update_score()
