import secrets
from typing import Any

from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

from core.models import User
from core.utils import get_last_promo
from core.views.forms import SelectDate, SelectDateTime
from core.views.widgets.ajax_select import AutoCompleteSelectUser
from subscription.models import Subscription


class SelectionDateForm(forms.Form):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["start_date"] = forms.DateTimeField(
            label=_("Start date"), widget=SelectDateTime, required=True
        )
        self.fields["end_date"] = forms.DateTimeField(
            label=_("End date"), widget=SelectDateTime, required=True
        )


class SubscriptionForm(forms.ModelForm):
    allowed_payment_methods = ["CARD", "CASH", "AE_ACCOUNT"]

    class Meta:
        model = Subscription
        fields = ["subscription_type", "payment_method", "location"]
        widgets = {"payment_method": forms.RadioSelect}

    def __init__(self, *args, initial=None, **kwargs):
        initial = initial or {}
        if "subscription_type" not in initial:
            initial["subscription_type"] = "deux-semestres"
        if "payment_method" not in initial:
            initial["payment_method"] = "CARD"
        super().__init__(*args, initial=initial, **kwargs)
        self.fields["payment_method"].choices = [
            m
            for m in settings.SITH_SUBSCRIPTION_PAYMENT_METHOD
            if m[0] in self.allowed_payment_methods
        ]
        self.fields["location"].choices = [
            m for m in settings.SITH_SUBSCRIPTION_LOCATIONS if m[0] != "EBOUTIC"
        ]

    def save(self, *args, **kwargs):
        if self.errors:
            # let django deal with the error messages
            return super().save(*args, **kwargs)

        duration, user = self.instance.semester_duration, self.instance.member
        self.instance.subscription_start = self.instance.compute_start(
            duration=duration, user=user
        )
        self.instance.subscription_end = self.instance.compute_end(
            duration=duration, start=self.instance.subscription_start, user=user
        )
        return super().save(*args, **kwargs)


class SubscriptionNewUserForm(SubscriptionForm):
    """Form to create subscriptions with the user they belong to.

    Examples:
        ```py
        assert not User.objects.filter(email=request.POST.get("email")).exists()
        form = SubscriptionNewUserForm(request.POST)
        if form.is_valid():
            form.save()

        # now the user exists and is subscribed
        user = User.objects.get(email=request.POST.get("email"))
        assert user.is_subscribed
    """

    allowed_payment_methods = ["CARD", "CASH"]

    __user_fields = forms.fields_for_model(
        User,
        ["first_name", "last_name", "email", "date_of_birth"],
        widgets={"date_of_birth": SelectDate},
    )
    first_name = __user_fields["first_name"]
    last_name = __user_fields["last_name"]
    email = __user_fields["email"]
    date_of_birth = __user_fields["date_of_birth"]

    field_order = [
        "first_name",
        "last_name",
        "email",
        "date_of_birth",
        "subscription_type",
        "payment_method",
        "location",
    ]

    def clean_email(self):
        email = self.cleaned_data["email"]
        if User.objects.filter(email=email).exists():
            raise ValidationError(_("A user with that email address already exists"))
        return email

    def clean(self) -> dict[str, Any]:
        """Initialize the [User][core.models.User] linked to this subscription.

        Warning:
            The `User` is initialized, but not saved.
            Don't use it for operations that expect
            a persisted object.
        """
        member = User(
            first_name=self.cleaned_data.get("first_name"),
            last_name=self.cleaned_data.get("last_name"),
            email=self.cleaned_data.get("email"),
            date_of_birth=self.cleaned_data.get("date_of_birth"),
        )
        if self.errors:
            # don't bother generating username, password and other data.
            # The form validation failed anyway, so using a dummy User
            # (just for Subscription.clean not to crash) is enough
            self.instance.member = member
            return super().clean()
        if self.cleaned_data.get("subscription_type") in [
            "un-semestre",
            "deux-semestres",
            "cursus-tronc-commun",
            "cursus-branche",
            "cursus-alternant",
        ]:
            member.role = "STUDENT"
            member.school = "UTBM"
        if self.cleaned_data.get("subscription_type") == "cursus-tronc-commun":
            member.promo = get_last_promo()
        if self.cleaned_data.get("subscription_type") in [
            "cursus-branche",
            "cursus-alternant",
        ]:
            member.promo = get_last_promo() - 2
        member.generate_username()
        member.set_password(secrets.token_urlsafe(nbytes=10))
        self.instance.member = member
        return super().clean()

    def save(self, *args, **kwargs):
        if self.errors:
            # let django deal with the error messages
            return super().save(*args, **kwargs)
        self.instance.member.save()
        return super().save(*args, **kwargs)


class SubscriptionExistingUserForm(SubscriptionForm):
    """Form to add a subscription to an existing user."""

    required_css_class = "required"

    birthdate = forms.fields_for_model(
        User,
        ["date_of_birth"],
        widgets={"date_of_birth": SelectDate(attrs={"hidden": True})},
        help_texts={"date_of_birth": _("This user didn't fill its birthdate yet.")},
    )["date_of_birth"]

    class Meta(SubscriptionForm.Meta):
        fields = ["member", *SubscriptionForm.Meta.fields]
        widgets = SubscriptionForm.Meta.widgets | {"member": AutoCompleteSelectUser}

    field_order = [
        "member",
        "birthdate",
        "subscription_type",
        "payment_method",
        "location",
    ]

    def __init__(self, *args, initial=None, **kwargs):
        super().__init__(*args, initial=initial, **kwargs)
        self.fields["birthdate"].required = True
        if not initial:
            return
        member: str | None = initial.get("member")
        if member and member.isdigit():
            member: User | None = User.objects.filter(id=int(member)).first()
        else:
            member = None
        if member and member.date_of_birth:
            # if there is an initial member with a birthdate,
            # there is no need to ask this to the user
            self.fields["birthdate"].initial = member.date_of_birth
        elif member:
            # if there is an initial member without a birthdate,
            # then the field must be displayed
            self.fields["birthdate"].widget.attrs.update({"hidden": False})
        # if there is no initial member, it means that it will be
        # dynamically selected using the AutoCompleteSelectUser widget.
        # JS will take care of un-hiding the field if necessary

    def save(self, *args, **kwargs):
        if self.errors:
            return super().save(*args, **kwargs)
        if (
            self.cleaned_data["birthdate"] is not None
            and self.instance.member.date_of_birth is None
        ):
            self.instance.member.date_of_birth = self.cleaned_data["birthdate"]
            self.instance.member.save()
        return super().save(*args, **kwargs)
