from datetime import datetime, time

from django import forms
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

from django_select2.forms import ModelSelect2MultipleWidget, Select2Widget
from dynamic_preferences.forms import PreferenceForm
from material import Fieldset, Layout, Row

from .mixins import ExtensibleForm
from .models import Announcement, Group, Person
from .registries import (
    group_preferences_registry,
    person_preferences_registry,
    site_preferences_registry,
)


class PersonAccountForm(forms.ModelForm):
    """Form to assign user accounts to persons in the frontend."""

    class Meta:
        model = Person
        fields = ["last_name", "first_name", "user"]
        widgets = {"user": Select2Widget}

    new_user = forms.CharField(required=False)

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

        # Fields displayed only for informational purposes
        self.fields["first_name"].disabled = True
        self.fields["last_name"].disabled = True

    def clean(self) -> None:
        User = get_user_model()

        if self.cleaned_data.get("new_user", None):
            if self.cleaned_data.get("user", None):
                # The user selected both an existing user and provided a name to create a new one
                self.add_error(
                    "new_user",
                    _("You cannot set a new username when also selecting an existing user."),
                )
            elif User.objects.filter(username=self.cleaned_data["new_user"]).exists():
                # The user tried to create a new user with the name of an existing user
                self.add_error("new_user", _("This username is already in use."))
            else:
                # Create new User object and assign to form field for existing user
                new_user_obj = User.objects.create_user(
                    self.cleaned_data["new_user"],
                    self.instance.email,
                    first_name=self.instance.first_name,
                    last_name=self.instance.last_name,
                )

                self.cleaned_data["user"] = new_user_obj


# Formset for batch-processing of assignments of users to persons
PersonsAccountsFormSet = forms.modelformset_factory(
    Person, form=PersonAccountForm, max_num=0, extra=0
)


class EditPersonForm(ExtensibleForm):
    """Form to edit an existing person object in the frontend."""

    layout = Layout(
        Fieldset(
            _("Base data"),
            "short_name",
            Row("user", "primary_group"),
            "is_active",
            Row("first_name", "additional_name", "last_name"),
        ),
        Fieldset(_("Address"), Row("street", "housenumber"), Row("postal_code", "place")),
        Fieldset(_("Contact data"), "email", Row("phone_number", "mobile_number")),
        Fieldset(
            _("Advanced personal data"),
            Row("sex", "date_of_birth"),
            Row("photo", "photo_cropping"),
            "guardians",
        ),
    )

    class Meta:
        model = Person
        fields = [
            "user",
            "is_active",
            "first_name",
            "last_name",
            "additional_name",
            "short_name",
            "street",
            "housenumber",
            "postal_code",
            "place",
            "phone_number",
            "mobile_number",
            "email",
            "date_of_birth",
            "sex",
            "photo",
            "photo_cropping",
            "guardians",
            "primary_group",
        ]
        widgets = {"user": Select2Widget}

    new_user = forms.CharField(
        required=False, label=_("New user"), help_text=_("Create a new account")
    )

    def clean(self) -> None:
        # Use code implemented in dedicated form to verify user selection
        return PersonAccountForm.clean(self)


class EditGroupForm(ExtensibleForm):
    """Form to edit an existing group in the frontend."""

    layout = Layout(
        Fieldset(_("Common data"), "name", "short_name"),
        Fieldset(_("Persons"), "members", "owners", "parent_groups"),
    )

    class Meta:
        model = Group
        exclude = []
        widgets = {
            "members": ModelSelect2MultipleWidget(
                search_fields=[
                    "first_name__icontains",
                    "last_name__icontains",
                    "short_name__icontains",
                ]
            ),
            "owners": ModelSelect2MultipleWidget(
                search_fields=[
                    "first_name__icontains",
                    "last_name__icontains",
                    "short_name__icontains",
                ]
            ),
            "parent_groups": ModelSelect2MultipleWidget(
                search_fields=["name__icontains", "short_name__icontains"]
            ),
        }


class AnnouncementForm(ExtensibleForm):
    """Form to create or edit an announcement in the frontend."""

    valid_from = forms.DateTimeField(required=False)
    valid_until = forms.DateTimeField(required=False)

    valid_from_date = forms.DateField(label=_("Date"))
    valid_from_time = forms.TimeField(label=_("Time"))

    valid_until_date = forms.DateField(label=_("Date"))
    valid_until_time = forms.TimeField(label=_("Time"))

    persons = forms.ModelMultipleChoiceField(
        Person.objects.all(), label=_("Persons"), required=False
    )
    groups = forms.ModelMultipleChoiceField(Group.objects.all(), label=_("Groups"), required=False)

    layout = Layout(
        Fieldset(
            _("From when until when should the announcement be displayed?"),
            Row("valid_from_date", "valid_from_time", "valid_until_date", "valid_until_time"),
        ),
        Fieldset(_("Who should see the announcement?"), Row("groups", "persons")),
        Fieldset(_("Write your announcement:"), "title", "description"),
    )

    def __init__(self, *args, **kwargs):
        if "instance" not in kwargs:
            # Default to today and whole day for new announcements
            kwargs["initial"] = {
                "valid_from_date": datetime.now(),
                "valid_from_time": time(0, 0),
                "valid_until_date": datetime.now(),
                "valid_until_time": time(23, 59),
            }
        else:
            announcement = kwargs["instance"]

            # Fill special fields from given announcement instance
            kwargs["initial"] = {
                "valid_from_date": announcement.valid_from.date(),
                "valid_from_time": announcement.valid_from.time(),
                "valid_until_date": announcement.valid_until.date(),
                "valid_until_time": announcement.valid_until.time(),
                "groups": announcement.get_recipients_for_model(Group),
                "persons": announcement.get_recipients_for_model(Person),
            }

        super().__init__(*args, **kwargs)

    def clean(self):
        data = super().clean()

        # Combine date and time fields into datetime objects
        valid_from = datetime.combine(data["valid_from_date"], data["valid_from_time"])
        valid_until = datetime.combine(data["valid_until_date"], data["valid_until_time"])

        # Sanity check validity range
        if valid_until < datetime.now():
            raise ValidationError(
                _("You are not allowed to create announcements which are only valid in the past.")
            )
        elif valid_from > valid_until:
            raise ValidationError(
                _("The from date and time must be earlier then the until date and time.")
            )

        # Inject real time data if all went well
        data["valid_from"] = valid_from
        data["valid_until"] = valid_until

        # Ensure at least one group or one person is set as recipient
        if "groups" not in data and "persons" not in data:
            raise ValidationError(_("You need at least one recipient."))

        # Unwrap all recipients into single user objects and generate final list
        data["recipients"] = []
        data["recipients"] += data.get("groups", [])
        data["recipients"] += data.get("persons", [])

        return data

    def save(self, _=False):
        # Save announcement, respecting data injected in clean()
        if self.instance is None:
            self.instance = Announcement()
        self.instance.valid_from = self.cleaned_data["valid_from"]
        self.instance.valid_until = self.cleaned_data["valid_until"]
        self.instance.title = self.cleaned_data["title"]
        self.instance.description = self.cleaned_data["description"]
        self.instance.save()

        # Save recipients
        self.instance.recipients.all().delete()
        for recipient in self.cleaned_data["recipients"]:
            self.instance.recipients.create(recipient=recipient)
        self.instance.save()

        return self.instance

    class Meta:
        model = Announcement
        exclude = []


class ChildGroupsForm(forms.Form):
    """Inline form for group editing to select child groups."""

    child_groups = forms.ModelMultipleChoiceField(queryset=Group.objects.all())


class SitePreferenceForm(PreferenceForm):
    """Form to edit site preferences."""

    registry = site_preferences_registry


class PersonPreferenceForm(PreferenceForm):
    """Form to edit preferences valid for one person."""

    registry = person_preferences_registry


class GroupPreferenceForm(PreferenceForm):
    """Form to edit preferences valid for members of a group."""

    registry = group_preferences_registry