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

from datetime import date

from django.conf import settings
from django.shortcuts import redirect
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.views.generic import DetailView, UpdateView, ListView
from django.views.generic.edit import DeleteView, FormMixin
from django.views.generic.base import TemplateView
from django.utils.translation import gettext_lazy as _
from django_tables2 import SingleTableView, MultiTableMixin
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from django.urls import reverse_lazy
from member.forms import ImageForm
import phonenumbers

from .models import Family, Challenge, FamilyMembership, User, Achievement
from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable, AchievementTable, FamilyAchievementTable
from .forms import ChallengeForm, FamilyMembershipForm, FamilyForm


class FamilyCreateView(ProtectQuerysetMixin, ProtectedCreateView):
    """
    Create family
    """
    model = Family
    extra_context = {"title": _('Create family')}
    form_class = FamilyForm

    def get_sample_object(self):
        return Family(
            name="",
            description="Sample family",
            score=0,
            rank=0,
        )

    def get_success_url(self):
        self.object.refresh_from_db()
        return reverse_lazy("family:manage")


class FamilyListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
    """
    List existing Families
    """
    model = Family
    table_class = FamilyTable
    extra_context = {"title": _('Families list')}

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        fake_family = Family(name="", description="")
        fake_challenge = Challenge(name="", description="", points=0)
        can_add_family = PermissionBackend.check_perm(self.request, "family.add_family", fake_family)
        can_add_challenge = PermissionBackend.check_perm(self.request, "family.add_challenge", fake_challenge)

        if Family.objects.exists() and Challenge.objects.exists():
            fake_achievement = Achievement(family=Family.objects.first(), challenge=Challenge.objects.first(), valid=False)
            can_add_achievement = PermissionBackend.check_perm(self.request, "family.add_achievement", fake_achievement)
        else:
            can_add_achievement = False

        context["can_manage"] = can_add_family or can_add_challenge or can_add_achievement

        return context


class FamilyDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
    """
    Display details of a family
    """
    model = Family
    context_object_name = "family"
    extra_context = {"title": _('Family detail')}

    def get_context_data(self, **kwargs):
        """
        Add members list
        """
        context = super().get_context_data(**kwargs)

        family = self.object

        # member list
        family_member = FamilyMembership.objects.filter(
            family=family,
            year=date.today().year,
        ).filter(PermissionBackend.filter_queryset(self.request, FamilyMembership, "view"))\
            .order_by("user__username")
        family_member = family_member.distinct("user__username")\
            if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else family_member

        membership_table = FamilyMembershipTable(data=family_member, prefix="membership-")
        membership_table.paginate(per_page=5, page=self.request.GET.get('membership-page', 1))
        context['member_list'] = membership_table

        # Check if the user has the right to create a membership, to display the button.
        empty_membership = FamilyMembership(
            family=family,
            user=User.objects.first(),
            year=date.today().year,
        )
        context["can_add_members"] = PermissionBackend()\
            .has_perm(self.request.user, "family.add_membership", empty_membership)

        # Défis réalisé par la famille
        achievements = Achievement.objects.filter(family=family)
        achievements_table = FamilyAchievementTable(data=achievements, prefix="achievement-")
        achievements_table.paginate(per_page=5, page=self.request.GET.get('achievement-page', 1))
        context["achievement_list"] = achievements_table

        return context


class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
    """
    Update the information of a family.
    """
    model = Family
    context_object_name = "family"
    form_class = FamilyForm
    extra_context = {"title": _('Update family')}

    def get_success_url(self):
        return reverse_lazy('family:family_detail', kwargs={'pk': self.object.pk})


class FamilyPictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, DetailView):
    """
    Update profile picture of the family
    """
    model = Family
    extra_context = {"title": _("Update family picture")}
    template_name = 'family/picture_update.html'
    form_class = ImageForm

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['form'] = self.form_class(self.request.POST, self.request.FILES)
        return context

    def get_success_url(self):
        """Redirect to family page after upload"""
        return reverse_lazy('family:family_detail', kwargs={'pk': self.object.pk})

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        self.object = self.get_object()
        return self.form_valid(form) if form.is_valid() else self.form_invalid(form)

    @transaction.atomic
    def form_valid(self, form):
        """
        Save the image
        """
        image = form.cleaned_data['image']

        if image is None:
            image = "pic/default.png"
        else:
            # Rename as PNG or GIF
            extension = image.name.split(".")[-1]
            if extension == "gif":
                image.name = "{}_pic.gif".format(self.object.pk)
            else:
                image.name = "{}_pic.png".format(self.object.pk)

        # Save
        self.object.display_image = image
        self.object.save()
        return super().form_valid(form)


class FamilyAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
    """
    Add a membership to a family
    """
    model = FamilyMembership
    form_class = FamilyMembershipForm
    template_name = 'family/add_member.html'
    extra_context = {"title": _("Add a new member to the family")}

    def get_sample_object(self):
        if "family_pk" in self.kwargs:
            family = Family.objects.get(pk=self.kwargs["family_pk"])
        else:
            family = FamilyMembership.objects.get(pk=self.kwargs["pk"]).family
        return FamilyMembership(
            user=self.request.user,
            family=family,
            year=date.today().year,
        )

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        family = Family.objects.filter(PermissionBackend.filter_queryset(self.request, Family, "view"))\
            .get(pk=self.kwargs['family_pk'])

        context['family'] = family

        return context

    @transaction.atomic
    def form_valid(self, form):
        """
        Create family membership, check that everythinf is good
        """
        family = Family.objects.filter(PermissionBackend.filter_queryset(self.request, Family, "view")) \
            .get(pk=self.kwargs["family_pk"])

        form.instance.family = family

        return super().form_valid(form)

    def get_success_url(self):
        return reverse_lazy('family:family_detail', kwargs={'pk': self.object.family.id})


class ChallengeCreateView(ProtectQuerysetMixin, ProtectedCreateView):
    """
    Create challenge
    """
    model = Challenge
    extra_context = {"title": _('Create challenge')}
    form_class = ChallengeForm

    def get_sample_object(self):
        return Challenge(
            name="",
            description="Sample challenge",
            points=0,
        )

    def get_success_url(self):
        return reverse_lazy('family:manage')


class ChallengeListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
    """
    List all challenges
    """
    model = Challenge
    table_class = ChallengeTable
    extra_context = {"title": _('Challenges list')}

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        fake_family = Family(name="", description="")
        fake_challenge = Challenge(name="", description="", points=0)
        can_add_family = PermissionBackend.check_perm(self.request, "family.add_family", fake_family)
        can_add_challenge = PermissionBackend.check_perm(self.request, "family.add_challenge", fake_challenge)

        if Family.objects.exists() and Challenge.objects.exists():
            fake_achievement = Achievement(family=Family.objects.first(), challenge=Challenge.objects.first(), valid=False)
            can_add_achievement = PermissionBackend.check_perm(self.request, "family.add_achievement", fake_achievement)
        else:
            can_add_achievement = False

        context["can_manage"] = can_add_family or can_add_challenge or can_add_achievement

        return context


class ChallengeDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
    """
    Display details of a challenge
    """
    model = Challenge
    context_object_name = "challenge"
    extra_context = {"title": _('Details of:')}

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        fields = ["name", "description", "points",]

        fields = dict([(field, getattr(self.object, field)) for field in fields])

        context["fields"] = [(
            Challenge._meta.get_field(field).verbose_name.capitalize(),
            value) for field, value in fields.items()]
        context["obtained"] = self.object.obtained
        context["update"] = PermissionBackend.check_perm(self.request, "family.change_challenge")

        return context


class ChallengeUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
    """
    Update the information of a challenge
    """
    model = Challenge
    context_object_name = "challenge"
    extra_context = {"title": _('Update challenge')}
    form_class = ChallengeForm

    def get_success_url(self, **kwargs):
        self.object.refresh_from_db()
        return reverse_lazy('family:challenge_detail', kwargs={'pk': self.object.pk})


class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
    """
    Manage families and challenges
    """
    model = Achievement
    template_name = 'family/manage.html'
    table_class = AchievementTable
    extra_context = {'title': _('Manage families and challenges')}

    def dispatch(self, request, *args, **kwargs):
        # Check that the user is authenticated
        if not request.user.is_authenticated:
            return self.handle_no_permission()

        perm = PermissionBackend.has_model_perm(self.request, Achievement(), "add")
        perm = perm or PermissionBackend.has_model_perm(self.request, Challenge(), "add")
        perm = perm or PermissionBackend.has_model_perm(self.request, Family(), "add")
        if not perm:
            raise PermissionDenied(_("You are not able to manage families and challenges."))
        return super().dispatch(request, *args, **kwargs)

    def get_queryset(self, **kwargs):
        # retrieves only Transaction that user has the right to see.
        return Achievement.objects.filter(
            PermissionBackend.filter_queryset(self.request, Achievement, "view")
        ).order_by("-obtained_at").all()

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        context['all_challenges'] = Challenge.objects.filter(
            PermissionBackend.filter_queryset(self.request, Challenge, "view")
        ).order_by('name')

        context["can_add_family"] = PermissionBackend.has_model_perm(self.request, Family(), "add")
        context["can_add_challenge"] = PermissionBackend.has_model_perm(self.request, Challenge(), "add")
        context["can_add_achievement"] = PermissionBackend.has_model_perm(self.request, Achievement(), "add")

        # Get the user's family if they have one
        try:
            user_family_membership = FamilyMembership.objects.get(user=self.request.user)
            context["user_family"] = user_family_membership.family
        except FamilyMembership.DoesNotExist:
            context["user_family"] = None

        phone_numbers = [
            u.profile.phone_number for u in User.objects.filter(
                memberships__roles__id=35,
                memberships__date_end__gte=date.today(),
                profile__phone_number__isnull=False
            ).distinct()
        ]
        formatted_phone_numbers = [phonenumbers.format_number(num, phonenumbers.PhoneNumberFormat.INTERNATIONAL) for num in phone_numbers if num]
        context["phone_numbers"] = formatted_phone_numbers

        return context

    def get_table(self, **kwargs):
        table = super().get_table(**kwargs)
        table.exclude = ('delete', 'validate',)
        table.orderable = False
        return table

    def get_table_data(self, **kwargs):
        qs = super().get_queryset(**kwargs)

        qs = qs.filter(PermissionBackend.filter_queryset(self.request, Achievement, "view"))

        return qs


class AchievementListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
    """
    List all achievements
    """
    model = Achievement
    tables = [AchievementTable, AchievementTable, ]
    extra_context = {'title': _('Achievement list')}

    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return self.handle_no_permission()

        if not PermissionBackend.has_model_perm(self.request, Achievement(), "change"):
            raise PermissionDenied(_("You are not able to see the achievement validation interface."))
        return super().dispatch(request, *args, **kwargs)

    def get_tables(self, **kwargs):
        tables = super().get_tables(**kwargs)

        tables[0].prefix = 'invalid-'
        tables[1].prefix = 'valid-'
        tables[1].exclude = ('validate', 'delete',)

        return tables

    def get_tables_data(self):
        table_valid = self.get_queryset().filter(valid=True)
        table_invalid = self.get_queryset().filter(valid=False)
        return [table_invalid, table_valid, ]

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        tables = context['tables']

        context['invalid'] = tables[0]
        context['valid'] = tables[1]
        return context


class AchievementValidateView(ProtectQuerysetMixin, LoginRequiredMixin, TemplateView):
    """
    Validate an achievement obtained by a family
    """
    template_name = 'family/achievement_confirm_validate.html'

    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return self.handle_no_permission()

        fake_achievement = Achievement(
            family=Family.objects.first(),
            challenge=Challenge.objects.first(),
            valid=False,
        )
        if not PermissionBackend.check_perm(self.request, "family.change_achievement_valid", fake_achievement):
            raise PermissionDenied()
        return super().dispatch(request, *args, **kwargs)

    def post(self, request, pk):
        achievement = Achievement.objects.get(pk=pk)

        achievement.valid = True
        achievement.save()

        return redirect(reverse_lazy('family:achievement_list'))


class AchievementDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView):
    """
    Delete an Achievement
    """
    model = Achievement

    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated:
            return self.handle_no_permission()

        fake_achievement = Achievement(
            family=Family.objects.first(),
            challenge=Challenge.objects.first(),
            valid=False,
        )
        if not PermissionBackend.check_perm(self.request, "family.change_achievement_valid", fake_achievement):
            raise PermissionDenied()
        return super().dispatch(request, *args, **kwargs)

    def get_success_url(self):
        return reverse_lazy('family:achievement_list')
