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

from datetime import timedelta

from api.viewsets import is_regex
from django_tables2.views import MultiTableMixin
from django.db import transaction
from django.db.models import Q
from django.http import HttpResponseRedirect, Http404
from django.views.generic import DetailView, UpdateView, CreateView
from django.views.generic.list import ListView
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from member.models import Club, Membership
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView, LoginRequiredMixin

from .models import Food, BasicFood, TransformedFood, QRCode
from .forms import QRCodeForms, BasicFoodForms, TransformedFoodForms, \
    ManageIngredientsForm, ManageIngredientsFormSet, AddIngredientForms, \
    BasicFoodUpdateForms, TransformedFoodUpdateForms
from .tables import FoodTable
from .utils import pretty_duration


class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
    """
    Display Food
    """
    model = Food
    tables = [FoodTable, FoodTable, FoodTable, ]
    extra_context = {"title": _('Food')}
    template_name = 'food/food_list.html'

    def get_queryset(self, **kwargs):
        return super().get_queryset(**kwargs).distinct()

    def get_tables(self):
        bureau_role_pk = 4
        clubs = Club.objects.filter(membership__in=Membership.objects.filter(
            user=self.request.user, roles=bureau_role_pk).filter(
                date_end__gte=timezone.now()))

        tables = [FoodTable] * (clubs.count() + 3)
        self.tables = tables
        tables = super().get_tables()
        tables[0].prefix = 'search-'
        tables[1].prefix = 'open-'
        tables[2].prefix = 'served-'
        for i in range(clubs.count()):
            tables[i + 3].prefix = clubs[i].name
        return tables

    def get_tables_data(self):
        # table search
        qs = self.get_queryset().order_by('name')
        if "search" in self.request.GET and self.request.GET['search']:
            pattern = self.request.GET['search']

            # check regex
            valid_regex = is_regex(pattern)
            suffix = '__iregex' if valid_regex else '__istartswith'
            prefix = '^' if valid_regex else ''
            qs = qs.filter(Q(**{f'name{suffix}': prefix + pattern}))
        else:
            qs = qs.none()
        search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Food, 'view'))
        # table open
        open_table = self.get_queryset().order_by('expiry_date').filter(
            Q(polymorphic_ctype__model='transformedfood')
            | Q(polymorphic_ctype__model='basicfood', basicfood__date_type='DLC')).filter(
                expiry_date__lt=timezone.now(), end_of_life='').filter(
                    PermissionBackend.filter_queryset(self.request, Food, 'view'))
        # table served
        served_table = self.get_queryset().order_by('-pk').filter(
            end_of_life='', is_ready=True).exclude(
                Q(polymorphic_ctype__model='basicfood',
                  basicfood__date_type='DLC',
                  expiry_date__lte=timezone.now(),)
                | Q(polymorphic_ctype__model='transformedfood',
                    expiry_date__lte=timezone.now(),
                    ))
        # tables club
        bureau_role_pk = 4
        clubs = Club.objects.filter(membership__in=Membership.objects.filter(
            user=self.request.user, roles=bureau_role_pk).filter(
                date_end__gte=timezone.now()))
        club_table = []
        for club in clubs:
            club_table.append(self.get_queryset().order_by('expiry_date').filter(
                owner=club, end_of_life='').filter(
                    PermissionBackend.filter_queryset(self.request, Food, 'view')
            ))
        return [search_table, open_table, served_table] + club_table

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

        tables = context['tables']
        # for extends base_search.html we need to name 'search_table' in 'table'
        for name, table in zip(['table', 'open', 'served'], tables):
            context[name] = table
        context['club_tables'] = tables[3:]

        context['can_add_meal'] = PermissionBackend.check_perm(self.request, 'food.transformedfood_add')
        return context


class QRCodeCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
    """
    A view to add qrcode
    """
    model = QRCode
    template_name = 'food/qrcode.html'
    form_class = QRCodeForms
    extra_context = {"title": _("Add a new QRCode")}

    def get(self, *args, **kwargs):
        qrcode = kwargs["slug"]
        if self.model.objects.filter(qr_code_number=qrcode).count() > 0:
            pk = self.model.objects.get(qr_code_number=qrcode).food_container.pk
            return HttpResponseRedirect(reverse_lazy("food:food_view", kwargs={"pk": pk}))
        else:
            return super().get(*args, **kwargs)

    @transaction.atomic
    def form_valid(self, form):
        qrcode_food_form = QRCodeForms(data=self.request.POST)
        if not qrcode_food_form.is_valid():
            return self.form_invalid(form)

        qrcode = form.save(commit=False)
        qrcode.qr_code_number = self.kwargs['slug']
        qrcode._force_save = True
        qrcode.save()
        qrcode.refresh_from_db()
        return super().form_valid(form)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['slug'] = self.kwargs['slug']

        # get last 10 BasicFood objects with distincts 'name' ordered by '-pk'
        # we can't use .distinct and .order_by with differents columns hence the generator
        context['last_items'] = [food for food in BasicFood.get_lastests_objects(10, 'name', '-pk')]
        return context

    def get_success_url(self, **kwargs):
        self.object.refresh_from_db()
        return reverse_lazy('food:food_view', kwargs={'pk': self.object.food_container.pk})

    def get_sample_object(self):
        return QRCode(
            qr_code_number=self.kwargs['slug'],
            food_container_id=1,
        )


class BasicFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
    """
    A view to add basicfood
    """
    model = BasicFood
    form_class = BasicFoodForms
    extra_context = {"title": _("Add an aliment")}
    template_name = "food/food_update.html"

    def get_sample_object(self):
        return BasicFood(
            name="",
            owner_id=1,
            expiry_date=timezone.now(),
            is_ready=True,
            arrival_date=timezone.now(),
            date_type='DLC',
        )

    @transaction.atomic
    def form_valid(self, form):
        if QRCode.objects.filter(qr_code_number=self.kwargs['slug']).count() > 0:
            return HttpResponseRedirect(reverse_lazy('food:qrcode_create', kwargs={'slug': self.kwargs['slug']}))
        food_form = BasicFoodForms(data=self.request.POST)
        if not food_form.is_valid():
            return self.form_invalid(form)

        food = form.save(commit=False)
        food.is_ready = False
        food.save()
        food.refresh_from_db()

        qrcode = QRCode()
        qrcode.qr_code_number = self.kwargs['slug']
        qrcode.food_container = food
        qrcode.save()

        return super().form_valid(form)

    def get_success_url(self, **kwargs):
        self.object.refresh_from_db()
        return reverse_lazy('food:basicfood_view', kwargs={"pk": self.object.pk})

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

        copy = self.request.GET.get('copy', None)
        if copy is not None:
            food = BasicFood.objects.get(pk=copy)
            print(context['form'].fields)
            for field in context['form'].fields:
                if field == 'allergens':
                    context['form'].fields[field].initial = getattr(food, field).all()
                else:
                    context['form'].fields[field].initial = getattr(food, field)

        return context


class TransformedFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
    """
    A view to add transformedfood
    """
    model = TransformedFood
    form_class = TransformedFoodForms
    extra_context = {"title": _("Add a meal")}
    template_name = "food/food_update.html"

    def get_sample_object(self):
        return TransformedFood(
            name="",
            owner_id=1,
            expiry_date=timezone.now(),
            is_ready=True,
        )

    @transaction.atomic
    def form_valid(self, form):
        form.instance.expiry_date = timezone.now() + timedelta(days=3)
        form.instance.is_ready = False
        return super().form_valid(form)

    def get_success_url(self, **kwargs):
        self.object.refresh_from_db()
        return reverse_lazy('food:transformedfood_view', kwargs={"pk": self.object.pk})


MAX_FORMS = 10


class ManageIngredientsView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
    """
    A view to manage ingredient for a transformed food
    """
    model = TransformedFood
    fields = ['ingredients']
    extra_context = {"title": _("Manage ingredients of:")}
    template_name = 'food/manage_ingredients.html'

    @transaction.atomic
    def form_valid(self, form):
        old_ingredients = list(self.object.ingredients.all()).copy()
        old_allergens = list(self.object.allergens.all()).copy()
        self.object.ingredients.clear()
        for i in range(self.object.ingredients.all().count() + 1 + MAX_FORMS):
            prefix = 'form-' + str(i) + '-'
            if form.data[prefix + 'qrcode'] not in ['0', '']:
                ingredient = QRCode.objects.get(pk=form.data[prefix + 'qrcode']).food_container
                self.object.ingredients.add(ingredient)
                if (prefix + 'fully_used') in form.data and form.data[prefix + 'fully_used'] == 'on':
                    ingredient.end_of_life = _('Fully used in {meal}'.format(
                        meal=self.object.name))
                    ingredient.save()

            elif form.data[prefix + 'name'] != '':
                ingredient = Food.objects.get(pk=form.data[prefix + 'name'])
                self.object.ingredients.add(ingredient)
                if (prefix + 'fully_used') in form.data and form.data[prefix + 'fully_used'] == 'on':
                    ingredient.end_of_life = _('Fully used in {meal}'.format(
                        meal=self.object.name))
                    ingredient.save()

        self.object.save(old_ingredients=old_ingredients, old_allergens=old_allergens)
        return HttpResponseRedirect(self.get_success_url())

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)
        context['title'] += ' ' + self.object.name
        formset = ManageIngredientsFormSet()
        ingredients = self.object.ingredients.all()
        formset.extra += ingredients.count() + MAX_FORMS
        context['form'] = ManageIngredientsForm()
        context['ingredients_count'] = ingredients.count()
        display = [True] * (1 + ingredients.count()) + [False] * (formset.extra - ingredients.count() - 1)
        context['formset'] = zip(display, formset)
        context['ingredients'] = []
        for ingredient in ingredients:
            qr = QRCode.objects.filter(food_container=ingredient)

            context['ingredients'].append({
                'food_pk': ingredient.pk,
                'food_name': ingredient.name,
                'qr_pk': '' if qr.count() == 0 else qr[0].pk,
                'qr_number': '' if qr.count() == 0 else qr[0].qr_code_number,
                'fully_used': 'true' if ingredient.end_of_life else '',
            })
        return context

    def get_success_url(self, **kwargs):
        return reverse_lazy('food:transformedfood_view', kwargs={"pk": self.object.pk})


class AddIngredientView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
    """
    A view to add ingredient to a meal
    """
    model = Food
    extra_context = {"title": _("Add the ingredient:")}
    form_class = AddIngredientForms
    template_name = 'food/food_update.html'

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)
        context['title'] += ' ' + self.object.name
        return context

    @transaction.atomic
    def form_valid(self, form):
        meals = TransformedFood.objects.filter(pk__in=form.data.getlist('ingredients')).all()
        if not meals:
            return HttpResponseRedirect(reverse_lazy('food:food_view', kwargs={"pk": self.object.pk}))
        for meal in meals:
            old_ingredients = list(meal.ingredients.all()).copy()
            old_allergens = list(meal.allergens.all()).copy()
            meal.ingredients.add(self.object.pk)
            # update allergen and expiry date if necessary
            if not (self.object.polymorphic_ctype.model == 'basicfood'
                    and self.object.date_type == 'DDM'):
                meal.expiry_date = min(meal.expiry_date, self.object.expiry_date)
            meal.allergens.set(meal.allergens.union(self.object.allergens.all()))
            meal.save(old_ingredients=old_ingredients, old_allergens=old_allergens)
            if 'fully_used' in form.data:
                if not self.object.end_of_life:
                    self.object.end_of_life = _(f'Food fully used in : {meal.name}')
                else:
                    self.object.end_of_life += ', ' + meal.name
        if 'fully_used' in form.data:
            self.object.is_ready = False
        self.object.save()
        # We redirect only the first parent
        parent_pk = meals[0].pk
        return HttpResponseRedirect(self.get_success_url(parent_pk=parent_pk))

    def get_success_url(self, **kwargs):
        return reverse_lazy('food:transformedfood_view', kwargs={"pk": kwargs['parent_pk']})


class FoodUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
    """
    A view to update Food
    """
    model = Food
    extra_context = {"title": _("Update an aliment")}
    template_name = 'food/food_update.html'

    @transaction.atomic
    def form_valid(self, form):
        form.instance.creater = self.request.user
        food = Food.objects.get(pk=self.kwargs['pk'])
        old_allergens = list(food.allergens.all()).copy()

        if food.polymorphic_ctype.model == 'transformedfood':
            old_ingredients = food.ingredients.all()
            form.instance.shelf_life = timedelta(
                seconds=int(form.data['shelf_life']) * 60 * 60)

        food_form = self.get_form_class()(data=self.request.POST)
        if not food_form.is_valid():
            return self.form_invalid(form)
        ans = super().form_valid(form)
        if food.polymorphic_ctype.model == 'transformedfood':
            form.instance.save(old_ingredients=old_ingredients)
        else:
            form.instance.save(old_allergens=old_allergens)
        return ans

    def get_form_class(self, **kwargs):
        food = Food.objects.get(pk=self.kwargs['pk'])
        if food.polymorphic_ctype.model == 'basicfood':
            return BasicFoodUpdateForms
        else:
            return TransformedFoodUpdateForms

    def get_form(self, **kwargs):
        form = super().get_form(**kwargs)
        if 'shelf_life' in form.initial:
            hours = form.initial['shelf_life'].days * 24 + form.initial['shelf_life'].seconds // 3600
            form.initial['shelf_life'] = hours
        return form

    def get_success_url(self, **kwargs):
        self.object.refresh_from_db()
        return reverse_lazy('food:food_view', kwargs={"pk": self.object.pk})


class FoodDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
    """
    A view to see a food
    """
    model = Food
    extra_context = {"title": _('Details of:')}
    context_object_name = "food"
    template_name = "food/food_detail.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        fields = ["name", "owner", "expiry_date", "allergens", "is_ready", "end_of_life", "order"]

        fields = dict([(field, getattr(self.object, field)) for field in fields])
        if fields["is_ready"]:
            fields["is_ready"] = _("Yes")
        else:
            fields["is_ready"] = _("No")
        fields["allergens"] = ", ".join(
            allergen.name for allergen in fields["allergens"].all())

        context["fields"] = [(
            Food._meta.get_field(field).verbose_name.capitalize(),
            value) for field, value in fields.items()]
        context["meals"] = self.object.transformed_ingredient_inv.all()
        context["update"] = PermissionBackend.check_perm(self.request, "food.change_food")
        context["add_ingredient"] = (self.object.end_of_life == '' and PermissionBackend.check_perm(self.request, "food.change_transformedfood"))
        return context

    def get(self, *args, **kwargs):
        if Food.objects.filter(pk=kwargs['pk']).count() != 1:
            return Http404
        model = Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model
        if 'stop_redirect' in kwargs and kwargs['stop_redirect']:
            return super().get(*args, **kwargs)
        kwargs = {'pk': kwargs['pk']}
        if model == 'basicfood':
            return HttpResponseRedirect(reverse_lazy("food:basicfood_view", kwargs=kwargs))
        return HttpResponseRedirect(reverse_lazy("food:transformedfood_view", kwargs=kwargs))


class BasicFoodDetailView(FoodDetailView):
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        fields = ['arrival_date', 'date_type']
        for field in fields:
            context["fields"].append((
                BasicFood._meta.get_field(field).verbose_name.capitalize(),
                getattr(self.object, field)
            ))
        return context

    def get(self, *args, **kwargs):
        if Food.objects.filter(pk=kwargs['pk']).count() == 1:
            kwargs['stop_redirect'] = (Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model == 'basicfood')
        return super().get(*args, **kwargs)


class TransformedFoodDetailView(FoodDetailView):
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["fields"].append((
            TransformedFood._meta.get_field("creation_date").verbose_name.capitalize(),
            self.object.creation_date
        ))
        context["fields"].append((
            TransformedFood._meta.get_field("shelf_life").verbose_name.capitalize(),
            pretty_duration(self.object.shelf_life)
        ))
        context["foods"] = self.object.ingredients.all()
        context["manage_ingredients"] = True
        return context

    def get(self, *args, **kwargs):
        if Food.objects.filter(pk=kwargs['pk']).count() == 1:
            kwargs['stop_redirect'] = (Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model == 'transformedfood')
        return super().get(*args, **kwargs)