# barbacompta - invoicing for dummies
# Copyright (C) 2019-2020 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


import datetime
from decimal import Decimal

from django import forms
from django.contrib.admin import widgets
from django.core.exceptions import ValidationError
from django.db.transaction import atomic

from . import models

pourcentages = [(Decimal(i) / 100, "%s %%" % i) for i in range(0, 101, 5)]


class RapidFactureForm(forms.Form):
    contrat = forms.ModelChoiceField(queryset=models.Contrat.objects.all())
    pourcentage = forms.ChoiceField(choices=pourcentages, initial='1')
    solde = forms.BooleanField(
        required=False, label='Ignorer le pourcentage, et solder le contrat en une ligne.'
    )

    def __init__(self, request=None, *args, **kwargs):
        self.request = request
        super().__init__(*args, **kwargs)

    @atomic
    def clean(self):
        contrat = self.cleaned_data['contrat']
        pourcentage = Decimal(self.cleaned_data['pourcentage'])
        solde = self.cleaned_data['solde']
        facture = models.Facture(contrat=contrat, creator=self.request.user, taux_tva=contrat.tva)
        try:
            facture.clean()
        except ValidationError as e:
            raise forms.ValidationError(*e.args)
        facture.save()
        self.cleaned_data["facture"] = facture
        lignes = []
        errors = []
        if solde:
            montant_solde = contrat.solde()
            if montant_solde == 0:
                raise ValidationError('Le solde du contrat est déjà nul.')
            models.Ligne.objects.create(
                facture=facture, intitule='Solde', prix_unitaire_ht=montant_solde, quantite=Decimal(1)
            )
        else:
            for prestation in contrat.prestations.all():
                ligne = models.Ligne(
                    facture=facture,
                    intitule=prestation.intitule,
                    prix_unitaire_ht=prestation.prix_unitaire_ht,
                    quantite=prestation.quantite * pourcentage,
                )
                lignes.append(ligne)
                try:
                    ligne.clean()
                except ValidationError as e:
                    error = "Il y a un problème avec la ligne « %s »: " % prestation.intitule
                    error += "; ".join(map(lambda x: x.rstrip("."), e.messages))
                    errors.append(error)
        if errors:
            raise forms.ValidationError(errors)

        for ligne in lignes:
            ligne.save()

        return self.cleaned_data


class DuplicateContractForm(forms.Form):
    contrat = forms.ModelChoiceField(queryset=models.Contrat.objects.all())
    new_intitule = forms.CharField(max_length=150, label="Nouvel intitulé")

    def __init__(self, request=None, *args, **kwargs):
        self.request = request
        super().__init__(*args, **kwargs)

    @atomic
    def clean(self):
        contrat = self.cleaned_data['contrat']
        new_intitule = self.cleaned_data['new_intitule']
        new_contrat = models.Contrat(
            client=contrat.client,
            intitule=new_intitule,
            description=contrat.description,
            tva=contrat.tva,
            creator=self.request.user,
        )
        try:
            new_contrat.clean()
        except ValidationError as e:
            raise forms.ValidationError(*e.args)
        new_contrat.save()
        self.cleaned_data["new_contrat"] = new_contrat
        for prestation in contrat.prestations.all():
            new_prestation = prestation.duplicate()
            new_prestation.contrat = new_contrat
            new_prestation.save()
        return self.cleaned_data


class LigneForm(forms.ModelForm):
    class Meta:
        fields = '__all__'
        model = models.Ligne
        localized_fields = ("quantite", "prix_unitaire_ht", "taux_tva")


class PrestationForm(forms.ModelForm):
    class Meta:
        fields = '__all__'
        model = models.Prestation
        localized_fields = ("quantite", "prix_unitaire_ht")


class FactureForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['contrat'].queryset = models.Contrat.objects.none()

        client_id = None

        if 'client' in self.data:
            try:
                client_id = int(self.data.get('client'))
            except (ValueError, TypeError):
                pass  # invalid input from client; ignore and fallback to empty Contrat queryset
        elif 'client' in self.initial:
            try:
                client_id = int(self.initial.get('client'))
            except (ValueError, TypeError):
                pass  # invalid input from client; ignore and fallback to empty Contrat queryset

        if client_id:
            self.fields['contrat'].queryset = models.Contrat.objects.filter(client_id=client_id)

    def clean_proforma(self):
        if not self.instance.proforma and 'proforma' in self.changed_data:
            raise ValidationError('Une facture ne peut pas redevenir proforma.')
        return self.cleaned_data['proforma']

    def clean_emission(self):
        if not self.instance.proforma and 'emission' in self.changed_data:
            raise ValidationError('Seules les factures proforma peuvent changer de date d\'émission.')
        return self.cleaned_data['emission']

    def clean(self):
        # réinitialiser la date d'émission quand une facture quitte le statut
        # proforma, sauf si une date d'émission spécifique a été fixée à la
        # main
        update_echeance = False
        if self.instance.proforma and 'proforma' in self.changed_data and 'emission' not in self.changed_data:
            self.cleaned_data['emission'] = models.today()
            update_echeance = True
        if (
            self.cleaned_data.get('emission')
            and 'emission' in self.changed_data
            and 'echeance' not in self.changed_data
        ):
            update_echeance = True
        if update_echeance:
            self.cleaned_data['echeance'] = self.cleaned_data['emission'] + datetime.timedelta(
                days=models.DELAI_PAIEMENT
            )

    class Meta:
        fields = '__all__'
        model = models.Facture
        localized_fields = ("taux_tva",)


class ClientForm(forms.ModelForm):
    class Meta:
        fields = '__all__'
        model = models.Client
        localized_fields = ("tva",)
        widgets = {
            "adresse": widgets.AdminTextareaWidget(attrs={"rows": 4}),
            "contacts": widgets.AdminTextareaWidget(attrs={"rows": 4}),
        }


class ContratForm(forms.ModelForm):
    class Meta:
        fields = '__all__'
        model = models.Contrat
        localized_fields = ("tva", "montant_sous_traite")


class PaymentForm(forms.ModelForm):
    class Meta:
        fields = '__all__'
        model = models.Payment
        localized_fields = ("montant_affecte",)
