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

from datetime import date

from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Q
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from note.models import NoteSpecial, SpecialTransaction, MembershipTransaction


class Invoice(models.Model):
    """
    An invoice model that can generates a true invoice.
    """

    id = models.PositiveIntegerField(
        primary_key=True,
        verbose_name=_("Invoice identifier"),
    )

    bde = models.CharField(
        max_length=32,
        default='Saperlistpopette.png',
        choices=(
            ('Saperlistpopette.png', 'Saper[list]popette'),
            ('Finalist.png', 'Fina[list]'),
            ('Listorique.png', '[List]orique'),
            ('Satellist.png', 'Satel[list]'),
            ('Monopolist.png', 'Monopo[list]'),
            ('Kataclist.png', 'Katac[list]'),
        ),
        verbose_name=_("BDE"),
    )

    object = models.CharField(
        max_length=255,
        verbose_name=_("Object"),
    )

    description = models.TextField(
        verbose_name=_("Description")
    )

    name = models.CharField(
        max_length=255,
        verbose_name=_("Name"),
    )

    address = models.TextField(
        verbose_name=_("Address"),
    )

    date = models.DateField(
        default=date.today,
        verbose_name=_("Date"),
    )

    acquitted = models.BooleanField(
        verbose_name=_("Acquitted"),
        default=False,
    )

    locked = models.BooleanField(
        verbose_name=_("Locked"),
        help_text=_("An invoice can't be edited when it is locked."),
        default=False,
    )

    tex = models.TextField(
        default="",
        verbose_name=_("tex source"),
    )

    def save(self, *args, **kwargs):
        """
        When an invoice is generated, we store the tex source.
        The advantage is to never change the template.
        Warning: editing this model regenerate the tex source, so be careful.
        """

        old_invoice = Invoice.objects.filter(id=self.id)
        if old_invoice.exists():
            if old_invoice.get().locked:
                raise ValidationError(_("This invoice is locked and can no longer be edited."))

        products = self.products.all()

        self.place = "Gif-sur-Yvette"
        self.my_name = "BDE ENS Cachan"
        self.my_address_street = "4 avenue des Sciences"
        self.my_city = "91190 Gif-sur-Yvette"
        self.bank_code = 30003
        self.desk_code = 3894
        self.account_number = 37280662
        self.rib_key = 14
        self.bic = "SOGEFRPP"

        # Fill the template with the information
        self.tex = render_to_string("treasury/invoice_sample.tex", dict(obj=self, products=products))

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

    class Meta:
        verbose_name = _("invoice")
        verbose_name_plural = _("invoices")


class Product(models.Model):
    """
    Product that appears on an invoice.
    """

    invoice = models.ForeignKey(
        Invoice,
        on_delete=models.CASCADE,
        related_name="products",
        verbose_name=_("invoice"),
    )

    designation = models.CharField(
        max_length=255,
        verbose_name=_("Designation"),
    )

    quantity = models.PositiveIntegerField(
        verbose_name=_("Quantity")
    )

    amount = models.IntegerField(
        verbose_name=_("Unit price")
    )

    @property
    def amount_euros(self):
        return "{:.2f}".format(self.amount / 100)

    @property
    def total(self):
        return self.quantity * self.amount

    @property
    def total_euros(self):
        return "{:.2f}".format(self.total / 100)

    class Meta:
        verbose_name = _("product")
        verbose_name_plural = _("products")


class RemittanceType(models.Model):
    """
    Store what kind of remittances can be stored.
    """

    note = models.OneToOneField(
        NoteSpecial,
        on_delete=models.CASCADE,
    )

    def __str__(self):
        return str(self.note)

    class Meta:
        verbose_name = _("remittance type")
        verbose_name_plural = _("remittance types")


class Remittance(models.Model):
    """
    Treasurers want to regroup checks or bank transfers in bank remittances.
    """

    date = models.DateTimeField(
        default=timezone.now,
        verbose_name=_("Date"),
    )

    remittance_type = models.ForeignKey(
        RemittanceType,
        on_delete=models.PROTECT,
        verbose_name=_("Type"),
    )

    comment = models.CharField(
        max_length=255,
        verbose_name=_("Comment"),
    )

    closed = models.BooleanField(
        default=False,
        verbose_name=_("Closed"),
    )

    class Meta:
        verbose_name = _("remittance")
        verbose_name_plural = _("remittances")

    @property
    def transactions(self):
        """
        :return: Transactions linked to this remittance.
        """
        if not self.pk:
            return SpecialTransaction.objects.none()
        return SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self)

    def count(self):
        """
        Linked transactions count.
        """
        return self.transactions.count()

    @property
    def amount(self):
        """
        Total amount of the remittance.
        """
        return sum(transaction.total for transaction in self.transactions.all())

    def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
        # Check if all transactions have the right type.
        if self.transactions.filter(~Q(source=self.remittance_type.note)).exists():
            raise ValidationError("All transactions in a remittance must have the same type")

        return super().save(force_insert, force_update, using, update_fields)

    def __str__(self):
        return _("Remittance #{:d}: {}").format(self.id, self.comment, )


class SpecialTransactionProxy(models.Model):
    """
    In order to keep modularity, we don't that the Note app depends on the treasury app.
    That's why we create a proxy in this app, to link special transactions and remittances.
    If it isn't very clean, it does what we want.
    """

    transaction = models.OneToOneField(
        SpecialTransaction,
        on_delete=models.CASCADE,
    )

    remittance = models.ForeignKey(
        Remittance,
        on_delete=models.PROTECT,
        null=True,
        verbose_name=_("Remittance"),
    )

    class Meta:
        verbose_name = _("special transaction proxy")
        verbose_name_plural = _("special transaction proxies")


class SogeCredit(models.Model):
    """
    Manage the credits from the Société générale.
    """
    user = models.OneToOneField(
        User,
        on_delete=models.PROTECT,
        verbose_name=_("user"),
    )

    transactions = models.ManyToManyField(
        MembershipTransaction,
        related_name="+",
        verbose_name=_("membership transactions"),
    )

    credit_transaction = models.OneToOneField(
        SpecialTransaction,
        on_delete=models.SET_NULL,
        verbose_name=_("credit transaction"),
        null=True,
    )

    @property
    def valid(self):
        return self.credit_transaction is not None

    @property
    def amount(self):
        return sum(transaction.total for transaction in self.transactions.all())

    def invalidate(self):
        """
        Invalidating a Société générale delete the transaction of the bank if it was already created.
        Treasurers must know what they do, With Great Power Comes Great Responsibility...
        """
        if self.valid:
            self.credit_transaction.valid = False
            self.credit_transaction._force_save = True
            self.credit_transaction.save()
            self.credit_transaction._force_delete = True
            self.credit_transaction.delete()
        self.credit_transaction = None
        for transaction in self.transactions.all():
            transaction.valid = False
            transaction._force_save = True
            transaction.save()

    def validate(self, force=False):
        if self.valid and not force:
            # The credit is already done
            return

        # First invalidate all transaction and delete the credit if already did (and force mode)
        self.invalidate()
        self.credit_transaction = SpecialTransaction.objects.create(
            source=NoteSpecial.objects.get(special_type="Virement bancaire"),
            destination=self.user.note,
            quantity=1,
            amount=self.amount,
            reason="Crédit société générale",
            last_name=self.user.last_name,
            first_name=self.user.first_name,
            bank="Société générale",
        )
        self.save()

        for transaction in self.transactions.all():
            transaction.valid = True
            transaction._force_save = True
            transaction.created_at = timezone.now()
            transaction.save()

    def delete(self, **kwargs):
        """
        Deleting a SogeCredit is equivalent to say that the Société générale didn't pay.
        Treasurers must know what they do, this is difficult to undo this operation.
        With Great Power Comes Great Responsibility...
        """

        total_fee = sum(transaction.total for transaction in self.transactions.all() if not transaction.valid)
        if self.user.note.balance < total_fee:
            raise ValidationError(_("This user doesn't have enough money to pay the memberships with its note. "
                                    "Please ask her/him to credit the note before invalidating this credit."))

        self.invalidate()
        for transaction in self.transactions.all():
            transaction._force_save = True
            transaction.valid = True
            transaction.created_at = timezone.now()
            transaction.save()
        super().delete(**kwargs)

    class Meta:
        verbose_name = _("Credit from the Société générale")
        verbose_name_plural = _("Credits from the Société générale")