from datetime import date, timedelta
from decimal import Decimal

from django import forms
from django.conf import settings
from django.contrib.auth.models import User
from django.core import validators
from django.core.exceptions import ValidationError
from django.forms import modelformset_factory
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from djconfig.forms import ConfigForm

from gestioncof.models import CofProfile
from kfet.models import (
    Account,
    Article,
    ArticleCategory,
    Checkout,
    CheckoutStatement,
    Operation,
    OperationGroup,
    Supplier,
    Transfer,
    TransferGroup,
)
from kfet.statistic import SCALE_CLASS_CHOICES

from . import KFET_DELETED_TRIGRAMME
from .auth import KFET_GENERIC_TRIGRAMME
from .auth.forms import UserGroupForm  # noqa

# -----
# Widgets
# -----


class DateTimeWidget(forms.DateTimeInput):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.attrs["format"] = "%Y-%m-%d %H:%M"

    class Media:
        css = {"all": ("kfet/vendor/bootstrap/bootstrap-datetimepicker.min.css",)}
        js = ("kfet/vendor/bootstrap/bootstrap-datetimepicker.min.js",)


# -----
# Account forms
# -----


def default_promo():
    now = date.today()
    return now.month <= 8 and now.year - 1 or now.year


def get_promo_choices():
    return [("", "Sans promo")] + [(r, r) for r in range(1980, date.today().year + 1)]


class AccountForm(forms.ModelForm):
    promo = forms.TypedChoiceField(
        choices=get_promo_choices,
        coerce=int,
        empty_value=None,
        initial=default_promo,
        required=False,
    )

    # Surcharge pour passer data à Account.save()
    def save(self, data={}, *args, **kwargs):
        obj = super().save(commit=False, *args, **kwargs)
        obj.save(data=data)
        return obj

    class Meta:
        model = Account
        fields = ["trigramme", "promo", "nickname"]
        widgets = {"trigramme": forms.TextInput(attrs={"autocomplete": "off"})}


class AccountBalanceForm(forms.ModelForm):
    class Meta:
        model = Account
        fields = ["balance"]


class AccountTriForm(AccountForm):
    def clean_trigramme(self):
        trigramme = self.cleaned_data["trigramme"]
        return trigramme.upper()

    class Meta(AccountForm.Meta):
        fields = ["trigramme"]


class AccountNoTriForm(AccountForm):
    class Meta(AccountForm.Meta):
        exclude = ["trigramme"]


class AccountPwdForm(forms.Form):
    pwd1 = forms.CharField(
        label="Mot de passe K-Fêt",
        required=False,
        help_text="Le mot de passe doit contenir au moins huit caractères",
        widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
        min_length=8,
    )
    pwd2 = forms.CharField(
        label="Confirmer le mot de passe",
        required=False,
        widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}),
    )

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

    def clean(self):
        pwd1 = self.cleaned_data.get("pwd1", "")
        pwd2 = self.cleaned_data.get("pwd2", "")
        if pwd1 != pwd2:
            self.add_error("pwd2", "Les mots de passe doivent être identiques !")
        super().clean()

    def save(self, commit=True):
        password = self.cleaned_data["pwd1"]
        self.account.change_pwd(password)
        if commit:
            self.account.save()

        return self.account


class AccountFrozenForm(forms.ModelForm):
    class Meta:
        model = Account
        fields = ["is_frozen"]


class CofForm(forms.ModelForm):
    def clean_is_cof(self):
        instance = getattr(self, "instance", None)
        if instance and instance.pk:
            return instance.is_cof
        else:
            return False

    class Meta:
        model = CofProfile
        fields = ["login_clipper", "is_cof", "departement"]


class UserForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ["username", "first_name", "last_name", "email"]
        help_texts = {"username": ""}


class UserRestrictForm(UserForm):
    class Meta(UserForm.Meta):
        fields = ["first_name", "last_name"]


class UserInfoForm(UserForm):
    first_name = forms.CharField(label="Prénom", disabled=True)
    last_name = forms.CharField(label="Nom de famille", disabled=True)

    class Meta(UserForm.Meta):
        fields = ["first_name", "last_name"]


# -----
# Checkout forms
# -----


class CheckoutForm(forms.ModelForm):
    class Meta:
        model = Checkout
        fields = ["name", "valid_from", "valid_to", "balance", "is_protected"]
        widgets = {"valid_from": DateTimeWidget(), "valid_to": DateTimeWidget()}


class CheckoutRestrictForm(CheckoutForm):
    class Meta(CheckoutForm.Meta):
        fields = ["name", "valid_from", "valid_to"]


class CheckoutStatementCreateForm(forms.ModelForm):
    balance_001 = forms.IntegerField(min_value=0, initial=0, required=False)
    balance_002 = forms.IntegerField(min_value=0, initial=0, required=False)
    balance_005 = forms.IntegerField(min_value=0, initial=0, required=False)
    balance_01 = forms.IntegerField(min_value=0, initial=0, required=False)
    balance_02 = forms.IntegerField(min_value=0, initial=0, required=False)
    balance_05 = forms.IntegerField(min_value=0, initial=0, required=False)
    balance_1 = forms.IntegerField(min_value=0, initial=0, required=False)
    balance_2 = forms.IntegerField(min_value=0, initial=0, required=False)
    balance_5 = forms.IntegerField(min_value=0, initial=0, required=False)
    balance_10 = forms.IntegerField(min_value=0, initial=0, required=False)
    balance_20 = forms.IntegerField(min_value=0, initial=0, required=False)
    balance_50 = forms.IntegerField(min_value=0, initial=0, required=False)
    balance_100 = forms.IntegerField(min_value=0, initial=0, required=False)
    balance_200 = forms.IntegerField(min_value=0, initial=0, required=False)
    balance_500 = forms.IntegerField(min_value=0, initial=0, required=False)

    class Meta:
        model = CheckoutStatement
        exclude = [
            "by",
            "at",
            "checkout",
            "amount_error",
            "amount_taken",
            "balance_old",
            "balance_new",
        ]

    def clean(self):
        not_count = self.cleaned_data["not_count"]
        if not not_count and (
            self.cleaned_data["balance_001"] is None
            or self.cleaned_data["balance_002"] is None
            or self.cleaned_data["balance_005"] is None
            or self.cleaned_data["balance_01"] is None
            or self.cleaned_data["balance_02"] is None
            or self.cleaned_data["balance_05"] is None
            or self.cleaned_data["balance_1"] is None
            or self.cleaned_data["balance_2"] is None
            or self.cleaned_data["balance_5"] is None
            or self.cleaned_data["balance_10"] is None
            or self.cleaned_data["balance_20"] is None
            or self.cleaned_data["balance_50"] is None
            or self.cleaned_data["balance_100"] is None
            or self.cleaned_data["balance_200"] is None
            or self.cleaned_data["balance_500"] is None
        ):
            raise ValidationError(
                "Y'a un problème. Si tu comptes la caisse, mets au moins des 0 stp."
            )
        super().clean()


class CheckoutStatementUpdateForm(forms.ModelForm):
    class Meta:
        model = CheckoutStatement
        exclude = ["by", "at", "checkout", "amount_error", "amount_taken"]


# -----
# Category
# -----


class CategoryForm(forms.ModelForm):
    class Meta:
        model = ArticleCategory
        fields = ["name", "has_addcost", "has_reduction"]


# -----
# Article forms
# -----


class ArticleForm(forms.ModelForm):
    category_new = forms.CharField(
        label="Créer une catégorie", max_length=45, required=False
    )
    category = forms.ModelChoiceField(
        label="Catégorie", queryset=ArticleCategory.objects.all(), required=False
    )

    suppliers = forms.ModelMultipleChoiceField(
        label="Fournisseurs", queryset=Supplier.objects.all(), required=False
    )
    supplier_new = forms.CharField(
        label="Créer un fournisseur", max_length=45, required=False
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.instance.pk:
            self.initial["suppliers"] = self.instance.suppliers.values_list(
                "pk", flat=True
            )

    def clean(self):
        category = self.cleaned_data.get("category")
        category_new = self.cleaned_data.get("category_new")

        if not category and not category_new:
            raise ValidationError("Sélectionnez une catégorie ou créez en une")
        elif not category:
            category, _ = ArticleCategory.objects.get_or_create(name=category_new)
            self.cleaned_data["category"] = category

        super().clean()

    class Meta:
        model = Article
        fields = [
            "name",
            "is_sold",
            "hidden",
            "price",
            "stock",
            "category",
            "box_type",
            "box_capacity",
        ]


class ArticleRestrictForm(ArticleForm):
    class Meta(ArticleForm.Meta):
        fields = [
            "name",
            "is_sold",
            "hidden",
            "price",
            "category",
            "box_type",
            "box_capacity",
        ]


# -----
# K-Psul forms
# -----


class KPsulOperationGroupForm(forms.ModelForm):
    # FIXME(AD): Use timezone.now instead of timezone.now() to avoid using a
    # fixed datetime (application boot here).
    # One may even use: Checkout.objects.is_valid() if changing
    # to now = timezone.now is ok in 'is_valid' definition.
    checkout = forms.ModelChoiceField(
        queryset=Checkout.objects.filter(
            is_protected=False,
            valid_from__lte=timezone.now(),
            valid_to__gte=timezone.now(),
        ),
        widget=forms.HiddenInput(),
    )
    on_acc = forms.ModelChoiceField(
        queryset=Account.objects.exclude(
            trigramme__in=[KFET_DELETED_TRIGRAMME, KFET_GENERIC_TRIGRAMME]
        ),
        widget=forms.HiddenInput(),
    )

    class Meta:
        model = OperationGroup
        fields = ["on_acc", "checkout", "comment"]


class KPsulAccountForm(forms.ModelForm):
    class Meta:
        model = Account
        fields = ["trigramme"]
        widgets = {
            "trigramme": forms.TextInput(
                attrs={"autocomplete": "off", "spellcheck": "false"}
            )
        }


class KPsulCheckoutForm(forms.Form):
    checkout = forms.ModelChoiceField(
        queryset=None, widget=forms.Select(attrs={"id": "id_checkout_select"})
    )

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

        # Create the queryset on form instanciation to use the current time.
        self.fields["checkout"].queryset = Checkout.objects.is_valid().filter(
            is_protected=False
        )


class KPsulOperationForm(forms.ModelForm):
    article = forms.ModelChoiceField(
        queryset=Article.objects.select_related("category").all(),
        required=False,
        widget=forms.HiddenInput(),
    )
    article_nb = forms.IntegerField(
        required=False,
        initial=None,
        validators=[validators.MinValueValidator(1)],
        widget=forms.HiddenInput(),
    )

    class Meta:
        model = Operation
        fields = ["type", "amount", "article", "article_nb"]
        widgets = {"type": forms.HiddenInput(), "amount": forms.HiddenInput()}

    def clean(self):
        super().clean()
        type_ope = self.cleaned_data.get("type")
        amount = self.cleaned_data.get("amount")
        article = self.cleaned_data.get("article")
        article_nb = self.cleaned_data.get("article_nb")
        errors = []
        if type_ope and type_ope == Operation.PURCHASE:
            if not article or article_nb is None or article_nb < 1:
                errors.append(
                    ValidationError("Un achat nécessite un article et une quantité")
                )
        elif type_ope and type_ope in [Operation.DEPOSIT, Operation.WITHDRAW]:
            if not amount or article or article_nb:
                errors.append(ValidationError("Bad request"))
            else:
                if type_ope == Operation.DEPOSIT and amount <= 0:
                    errors.append(ValidationError("Charge non positive"))
                elif type_ope == Operation.WITHDRAW and amount >= 0:
                    errors.append(ValidationError("Retrait non négatif"))
            self.cleaned_data["article"] = None
            self.cleaned_data["article_nb"] = None

        if errors:
            raise ValidationError(errors)


KPsulOperationFormSet = modelformset_factory(
    Operation,
    form=KPsulOperationForm,
    can_delete=True,
    extra=0,
    min_num=1,
    validate_min=True,
)


class AddcostForm(forms.Form):
    trigramme = forms.CharField(required=False)
    amount = forms.DecimalField(
        required=False, max_digits=6, decimal_places=2, min_value=Decimal(0)
    )

    def clean(self):
        trigramme = self.cleaned_data.get("trigramme")
        if trigramme:
            try:
                Account.objects.get(trigramme=trigramme)
            except Account.DoesNotExist:
                raise ValidationError("Compte invalide")
        else:
            self.cleaned_data["amount"] = 0
        super().clean()


# -----
# Settings forms
# -----


class KFetConfigForm(ConfigForm):

    kfet_reduction_cof = forms.DecimalField(
        label="Réduction COF",
        initial=Decimal("20"),
        max_digits=6,
        decimal_places=2,
        help_text="Réduction, à donner en pourcentage, appliquée lors d'un "
        "achat par un-e membre du COF sur le montant en euros.",
    )
    kfet_addcost_amount = forms.DecimalField(
        label="Montant de la majoration (en €)",
        initial=Decimal("0"),
        required=False,
        max_digits=6,
        decimal_places=2,
    )
    kfet_addcost_for = forms.ModelChoiceField(
        label="Destinataire de la majoration",
        initial=None,
        required=False,
        help_text="Laissez vide pour désactiver la majoration.",
        queryset=(
            Account.objects.select_related("cofprofile", "cofprofile__user").all()
        ),
    )
    kfet_overdraft_duration = forms.DurationField(
        label="Durée du découvert autorisé par défaut", initial=timedelta(days=1)
    )
    kfet_overdraft_amount = forms.DecimalField(
        label="Montant du découvert autorisé par défaut (en €)",
        initial=Decimal("20"),
        max_digits=6,
        decimal_places=2,
    )
    kfet_cancel_duration = forms.DurationField(
        label="Durée pour annuler une commande sans mot de passe",
        initial=timedelta(minutes=5),
    )


class FilterHistoryForm(forms.Form):
    start = forms.DateTimeField(
        label=_("De"),
        widget=DateTimeWidget,
        required=False,
        help_text="Limité à {} jours ({} pour les chefs/trez)".format(
            settings.KFET_HISTORY_DATE_LIMIT.days,
            settings.KFET_HISTORY_LONG_DATE_LIMIT.days,
        ),
    )
    end = forms.DateTimeField(label=_("À"), widget=DateTimeWidget, required=False)
    checkout = forms.ModelChoiceField(
        label=_("Caisse"),
        queryset=Checkout.objects.all(),
        required=False,
        empty_label=_("Toutes les caisses"),
    )
    account = forms.ModelChoiceField(
        label=_("Compte"),
        queryset=Account.objects.all(),
        required=False,
        empty_label=_("Tous les comptes"),
    )

    transfers_only = forms.BooleanField(widget=forms.HiddenInput, required=False)
    opes_only = forms.BooleanField(widget=forms.HiddenInput, required=False)


# -----
# Transfer forms
# -----


class TransferGroupForm(forms.ModelForm):
    class Meta:
        model = TransferGroup
        fields = ["comment"]


class TransferForm(forms.ModelForm):
    from_acc = forms.ModelChoiceField(
        queryset=Account.objects.exclude(trigramme__in=["LIQ", "#13", "GNR"]),
        widget=forms.HiddenInput(),
    )
    to_acc = forms.ModelChoiceField(
        queryset=Account.objects.exclude(trigramme__in=["LIQ", "#13", "GNR"]),
        widget=forms.HiddenInput(),
    )

    def clean_amount(self):
        amount = self.cleaned_data["amount"]
        if amount <= 0:
            raise forms.ValidationError("Le montant d'un transfert doit être positif")
        return amount

    class Meta:
        model = Transfer
        fields = ["from_acc", "to_acc", "amount"]


TransferFormSet = modelformset_factory(
    Transfer, form=TransferForm, min_num=1, validate_min=True, extra=9
)

# -----
# Inventory forms
# -----


class InventoryArticleForm(forms.Form):
    article = forms.ModelChoiceField(
        queryset=Article.objects.all(), widget=forms.HiddenInput()
    )
    stock_new = forms.IntegerField(required=False)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if "initial" in kwargs:
            self.name = kwargs["initial"]["name"]
            self.stock_old = kwargs["initial"]["stock_old"]
            self.category = kwargs["initial"]["category"]
            self.category_name = kwargs["initial"]["category__name"]
            self.box_capacity = kwargs["initial"]["box_capacity"]
            self.is_sold = kwargs["initial"]["is_sold"]


# -----
# Order forms
# -----


class OrderArticleForm(forms.Form):
    article = forms.ModelChoiceField(
        queryset=Article.objects.all(), widget=forms.HiddenInput()
    )
    quantity_ordered = forms.IntegerField(required=False)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if "initial" in kwargs:
            self.name = kwargs["initial"]["name"]
            self.stock = kwargs["initial"]["stock"]
            self.category = kwargs["initial"]["category"]
            self.category_name = kwargs["initial"]["category__name"]
            self.box_capacity = kwargs["initial"]["box_capacity"]
            self.v_all = kwargs["initial"]["v_all"]
            self.v_moy = kwargs["initial"]["v_moy"]
            self.v_et = kwargs["initial"]["v_et"]
            self.v_prev = kwargs["initial"]["v_prev"]
            self.c_rec = kwargs["initial"]["c_rec"]
            self.is_sold = kwargs["initial"]["is_sold"]


class OrderArticleToInventoryForm(forms.Form):
    article = forms.ModelChoiceField(
        queryset=Article.objects.all(), widget=forms.HiddenInput()
    )
    price_HT = forms.DecimalField(max_digits=7, decimal_places=4, required=False)
    TVA = forms.DecimalField(max_digits=7, decimal_places=2, required=False)
    rights = forms.DecimalField(max_digits=7, decimal_places=4, required=False)
    quantity_received = forms.IntegerField()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if "initial" in kwargs:
            self.name = kwargs["initial"]["name"]
            self.category = kwargs["initial"]["category"]
            self.category_name = kwargs["initial"]["category__name"]
            self.quantity_ordered = kwargs["initial"]["quantity_ordered"]


# ----
# Formulaires pour les statistiques K-Fêt
# ----


class StatScaleForm(forms.Form):
    """Formulaire pour nettoyer les paramètres envoyés aux
    vues de statistiques K-Fêt. Non destiné à être affiché.
    """

    name = forms.ChoiceField(choices=SCALE_CLASS_CHOICES)
    begin = forms.DateTimeField(required=False)
    end = forms.DateTimeField(required=False)
    n_steps = forms.IntegerField(required=False)
    last = forms.BooleanField(required=False)


class AccountStatForm(forms.Form):
    """Idem, mais pour la balance d'un compte"""

    begin_date = forms.DateTimeField(required=False)
    end_date = forms.DateTimeField(required=False)
    last_days = forms.IntegerField(required=False)
