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

import os
import shutil
import subprocess
from tempfile import mkdtemp

from crispy_forms.helper import FormHelper
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ValidationError, PermissionDenied
from django.db import transaction
from django.db.models import Q
from django.forms import Form
from django.http import HttpResponse
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic import UpdateView, DetailView
from django.views.generic.base import View, TemplateView
from django.views.generic.edit import BaseFormView, DeleteView
from django_tables2 import SingleTableView
from note.models import SpecialTransaction, NoteSpecial, Alias
from note_kfet.settings.base import BASE_DIR
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView

from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm, LinkTransactionToRemittanceForm
from .models import Invoice, Product, Remittance, SpecialTransactionProxy, SogeCredit
from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable, SogeCreditTable


class InvoiceCreateView(ProtectQuerysetMixin, ProtectedCreateView):
    """
    Create Invoice
    """
    model = Invoice
    form_class = InvoiceForm
    extra_context = {"title": _("Create new invoice")}

    def get_sample_object(self):
        return Invoice(
            id=0,
            object="",
            description="",
            name="",
            address="",
        )

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

        form = context['form']
        form.helper = FormHelper()
        # Remove form tag on the generation of the form in the template (already present on the template)
        form.helper.form_tag = False
        # The formset handles the set of the products
        form_set = ProductFormSet(instance=form.instance)
        context['formset'] = form_set
        context['helper'] = ProductFormSetHelper()

        return context

    def get_form(self, form_class=None):
        form = super().get_form(form_class)
        del form.fields["locked"]
        return form

    @transaction.atomic
    def form_valid(self, form):
        ret = super().form_valid(form)

        # For each product, we save it
        formset = ProductFormSet(self.request.POST, instance=form.instance)
        if formset.is_valid():
            for f in formset:
                # We don't save the product if the designation is not entered, ie. if the line is empty
                if f.is_valid() and f.instance.designation:
                    f.save()
                    f.instance.save()
                else:
                    f.instance = None

        return ret

    def get_success_url(self):
        return reverse_lazy('treasury:invoice_list')


class InvoiceListView(LoginRequiredMixin, SingleTableView):
    """
    List existing Invoices
    """
    model = Invoice
    table_class = InvoiceTable
    extra_context = {"title": _("Invoices list")}

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

        sample_invoice = Invoice(
            id=0,
            object="",
            description="",
            name="",
            address="",
        )
        if not PermissionBackend.check_perm(self.request.user, "treasury.add_invoice", sample_invoice):
            raise PermissionDenied(_("You are not able to see the treasury interface."))
        return super().dispatch(request, *args, **kwargs)


class InvoiceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
    """
    Create Invoice
    """
    model = Invoice
    form_class = InvoiceForm
    extra_context = {"title": _("Update an invoice")}

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

        form = context['form']
        form.helper = FormHelper()
        # Remove form tag on the generation of the form in the template (already present on the template)
        form.helper.form_tag = False
        # The formset handles the set of the products
        form_set = ProductFormSet(instance=self.object)
        context['formset'] = form_set
        context['helper'] = ProductFormSetHelper()

        if self.object.locked:
            for field_name in form.fields:
                form.fields[field_name].disabled = True
            for f in form_set.forms:
                for field_name in f.fields:
                    f.fields[field_name].disabled = True

        return context

    def get_form(self, form_class=None):
        form = super().get_form(form_class)
        del form.fields["id"]
        return form

    @transaction.atomic
    def form_valid(self, form):
        ret = super().form_valid(form)

        formset = ProductFormSet(self.request.POST, instance=form.instance)
        saved = []
        # For each product, we save it
        if formset.is_valid():
            for f in formset:
                # We don't save the product if the designation is not entered, ie. if the line is empty
                if f.is_valid() and f.instance.designation:
                    f.save()
                    f.instance.save()
                    saved.append(f.instance.pk)
                else:
                    f.instance = None
            # Remove old products that weren't given in the form
            Product.objects.filter(~Q(pk__in=saved), invoice=form.instance).delete()

        return ret

    def get_success_url(self):
        return reverse_lazy('treasury:invoice_list')


class InvoiceDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView):
    """
    Delete a non-validated WEI registration
    """
    model = Invoice
    extra_context = {"title": _("Delete invoice")}

    def delete(self, request, *args, **kwargs):
        if self.get_object().locked:
            raise PermissionDenied(_("This invoice is locked and can't be deleted."))
        return super().delete(request, *args, **kwargs)

    def get_success_url(self):
        return reverse_lazy('treasury:invoice_list')


class InvoiceRenderView(LoginRequiredMixin, View):
    """
    Render Invoice as a generated PDF with the given information and a LaTeX template
    """

    def get(self, request, **kwargs):
        pk = kwargs["pk"]
        invoice = Invoice.objects.filter(PermissionBackend.filter_queryset(request.user, Invoice, "view")).get(pk=pk)
        tex = invoice.tex

        try:
            os.mkdir(BASE_DIR + "/tmp")
        except FileExistsError:
            pass
        # We render the file in a temporary directory
        tmp_dir = mkdtemp(prefix=BASE_DIR + "/tmp/")

        try:
            with open("{}/invoice-{:d}.tex".format(tmp_dir, pk), "wb") as f:
                f.write(tex.encode("UTF-8"))
            del tex

            # The file has to be rendered twice
            for ignored in range(2):
                error = subprocess.Popen(
                    ["/usr/bin/xelatex", "-interaction=nonstopmode", "invoice-{}.tex".format(pk)],
                    cwd=tmp_dir,
                    stdin=open(os.devnull, "r"),
                    stderr=open(os.devnull, "wb"),
                    stdout=open(os.devnull, "wb"),
                ).wait()

                if error:
                    with open("{}/invoice-{:d}.log".format(tmp_dir, pk), "r") as f:
                        log = f.read()
                    raise IOError("An error attempted while generating a invoice (code=" + str(error) + ")\n\n" + log)

            # Display the generated pdf as a HTTP Response
            pdf = open("{}/invoice-{}.pdf".format(tmp_dir, pk), 'rb').read()
            response = HttpResponse(pdf, content_type="application/pdf")
            response['Content-Disposition'] = "inline;filename=Facture%20n°{:d}.pdf".format(pk)
        except IOError as e:
            raise e
        finally:
            # Delete all temporary files
            shutil.rmtree(tmp_dir)

        return response


class RemittanceCreateView(ProtectQuerysetMixin, ProtectedCreateView):
    """
    Create Remittance
    """
    model = Remittance
    form_class = RemittanceForm
    extra_context = {"title": _("Create a new remittance")}

    def get_sample_object(self):
        return Remittance(
            remittance_type_id=1,
            comment="",
        )

    def get_success_url(self):
        return reverse_lazy('treasury:remittance_list')

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

        context["table"] = RemittanceTable(
            data=Remittance.objects.filter(
                PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all())
        context["special_transactions"] = SpecialTransactionTable(data=SpecialTransaction.objects.none())

        return context


class RemittanceListView(LoginRequiredMixin, TemplateView):
    """
    List existing Remittances
    """
    template_name = "treasury/remittance_list.html"
    extra_context = {"title": _("Remittances list")}

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

        sample_remittance = Remittance(
            remittance_type_id=1,
            comment="",
        )
        if not PermissionBackend.check_perm(self.request.user, "treasury.add_remittance", sample_remittance):
            raise PermissionDenied(_("You are not able to see the treasury interface."))
        return super().dispatch(request, *args, **kwargs)

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

        opened_remittances = RemittanceTable(
            data=Remittance.objects.filter(closed=False).filter(
                PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all(),
            prefix="opened-remittances-",
        )
        opened_remittances.paginate(page=self.request.GET.get("opened-remittances-page", 1), per_page=10)
        context["opened_remittances"] = opened_remittances

        closed_remittances = RemittanceTable(
            data=Remittance.objects.filter(closed=True).filter(
                PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all(),
            prefix="closed-remittances-",
        )
        closed_remittances.paginate(page=self.request.GET.get("closed-remittances-page", 1), per_page=10)
        context["closed_remittances"] = closed_remittances

        no_remittance_tr = SpecialTransactionTable(
            data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
                                                   specialtransactionproxy__remittance=None).filter(
                PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all(),
            exclude=('remittance_remove', ),
            prefix="no-remittance-",
        )
        no_remittance_tr.paginate(page=self.request.GET.get("no-remittance-page", 1), per_page=10)
        context["special_transactions_no_remittance"] = no_remittance_tr

        with_remittance_tr = SpecialTransactionTable(
            data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
                                                   specialtransactionproxy__remittance__closed=False).filter(
                PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all(),
            exclude=('remittance_add', ),
            prefix="with-remittance-",
        )
        with_remittance_tr.paginate(page=self.request.GET.get("with-remittance-page", 1), per_page=10)
        context["special_transactions_with_remittance"] = with_remittance_tr

        return context


class RemittanceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
    """
    Update Remittance
    """
    model = Remittance
    form_class = RemittanceForm
    extra_context = {"title": _("Update a remittance")}

    def get_success_url(self):
        return reverse_lazy('treasury:remittance_list')

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

        data = SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).filter(
            PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all()
        context["special_transactions"] = SpecialTransactionTable(
            data=data,
            exclude=('remittance_add', 'remittance_remove', ) if self.object.closed else ('remittance_add', ))

        return context


class LinkTransactionToRemittanceView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
    """
    Attach a special transaction to a remittance
    """
    model = SpecialTransactionProxy
    form_class = LinkTransactionToRemittanceForm
    extra_context = {"title": _("Attach a transaction to a remittance")}

    def get_success_url(self):
        return reverse_lazy('treasury:remittance_list')

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

        form = context["form"]
        form.fields["last_name"].initial = self.object.transaction.last_name
        form.fields["first_name"].initial = self.object.transaction.first_name
        form.fields["bank"].initial = self.object.transaction.bank
        form.fields["amount"].initial = self.object.transaction.amount
        form.fields["remittance"].queryset = form.fields["remittance"] \
            .queryset.filter(remittance_type__note=self.object.transaction.source)

        return context


class UnlinkTransactionToRemittanceView(LoginRequiredMixin, View):
    """
    Unlink a special transaction and its remittance
    """

    def get(self, *args, **kwargs):
        pk = kwargs["pk"]
        transaction = SpecialTransactionProxy.objects.get(pk=pk)

        # The remittance must be open (or inexistant)
        if transaction.remittance and transaction.remittance.closed:
            raise ValidationError("Remittance is already closed.")

        transaction.remittance = None
        transaction.save()

        return redirect('treasury:remittance_list')


class SogeCreditListView(LoginRequiredMixin, ProtectQuerysetMixin, SingleTableView):
    """
    List all Société Générale credits
    """
    model = SogeCredit
    table_class = SogeCreditTable
    extra_context = {"title": _("List of credits from the Société générale")}

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

        if not super().get_queryset().exists():
            raise PermissionDenied(_("You are not able to see the treasury interface."))
        return super().dispatch(request, *args, **kwargs)

    def get_queryset(self, **kwargs):
        """
        Filter the table with the given parameter.
        :param kwargs:
        :return:
        """
        qs = super().get_queryset()
        if "search" in self.request.GET:
            pattern = self.request.GET["search"]
            if pattern:
                qs = qs.filter(
                    Q(user__first_name__iregex=pattern)
                    | Q(user__last_name__iregex=pattern)
                    | Q(user__note__alias__name__iregex="^" + pattern)
                    | Q(user__note__alias__normalized_name__iregex="^" + Alias.normalize(pattern))
                )

        if "valid" not in self.request.GET or not self.request.GET["valid"]:
            qs = qs.filter(credit_transaction__valid=False)

        return qs


class SogeCreditManageView(LoginRequiredMixin, ProtectQuerysetMixin, BaseFormView, DetailView):
    """
    Manage credits from the Société générale.
    """
    model = SogeCredit
    form_class = Form
    extra_context = {"title": _("Manage credits from the Société générale")}

    @transaction.atomic
    def form_valid(self, form):
        if "validate" in form.data:
            self.get_object().validate(True)
        elif "delete" in form.data:
            self.get_object().delete()
        return super().form_valid(form)

    def get_success_url(self):
        if "validate" in self.request.POST:
            return reverse_lazy('treasury:manage_soge_credit', args=(self.get_object().pk,))
        return reverse_lazy('treasury:soge_credits')