from copy import deepcopy

from apps.accounts.models import Account
from apps.common.fields.forms.dynamic_select import (
    DynamicModelChoiceField,
    DynamicModelMultipleChoiceField,
)
from apps.common.middleware.thread_local import get_current_user
from apps.common.widgets.crispy.daisyui import Switch
from apps.common.widgets.crispy.submit import NoClassSubmit
from apps.common.widgets.datepicker import AirDatePickerInput, AirMonthYearPickerInput
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.common.widgets.tom_select import TomSelect
from apps.rules.signals import transaction_created, transaction_updated
from apps.transactions.models import (
    InstallmentPlan,
    TransactionAttachment,
    QuickTransaction,
    RecurringTransaction,
    Transaction,
    TransactionCategory,
    TransactionEntity,
    TransactionTag,
)
from crispy_forms.bootstrap import AccordionGroup, AppendedText, FormActions, Accordion
from crispy_forms.helper import FormHelper
from crispy_forms.layout import (
    HTML,
    Column,
    Div,
    Field,
    Layout,
    Row,
)
from django import forms
from django.db.models import Q
from django.utils.translation import gettext_lazy as _


class MultipleFileInput(forms.ClearableFileInput):
    allow_multiple_selected = True


class MultipleFileField(forms.FileField):
    widget = MultipleFileInput

    def clean(self, data, initial=None):
        single_file_clean = super().clean
        if isinstance(data, (list, tuple)):
            return [single_file_clean(file, initial) for file in data]
        if data:
            return [single_file_clean(data, initial)]
        return []


class TransactionForm(forms.ModelForm):
    category = DynamicModelChoiceField(
        create_field="name",
        model=TransactionCategory,
        required=False,
        label=_("Category"),
        queryset=TransactionCategory.objects.filter(active=True),
    )
    tags = DynamicModelMultipleChoiceField(
        model=TransactionTag,
        to_field_name="name",
        create_field="name",
        required=False,
        label=_("Tags"),
        queryset=TransactionTag.objects.filter(active=True),
    )
    entities = DynamicModelMultipleChoiceField(
        model=TransactionEntity,
        to_field_name="name",
        create_field="name",
        required=False,
        label=_("Entities"),
    )
    account = forms.ModelChoiceField(
        queryset=Account.objects.filter(is_archived=False),
        label=_("Account"),
        widget=TomSelect(clear_button=False, group_by="group"),
    )

    date = forms.DateField(label=_("Date"))

    reference_date = forms.DateField(
        widget=AirMonthYearPickerInput(),
        label=_("Reference Date"),
        required=False,
    )

    class Meta:
        model = Transaction
        fields = [
            "account",
            "type",
            "is_paid",
            "date",
            "reference_date",
            "amount",
            "description",
            "notes",
            "category",
            "tags",
            "entities",
        ]
        widgets = {
            "notes": forms.Textarea(attrs={"rows": 3}),
            "account": TomSelect(clear_button=False, group_by="group"),
        }

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

        # if editing a transaction display non-archived items and it's own item even if it's archived
        if self.instance.id:
            self.fields["account"].queryset = Account.objects.filter(
                Q(is_archived=False) | Q(transactions=self.instance.id),
            )

            self.fields["category"].queryset = TransactionCategory.objects.filter(
                Q(active=True) | Q(transaction=self.instance.id)
            )

            self.fields["tags"].queryset = TransactionTag.objects.filter(
                Q(active=True) | Q(transaction=self.instance.id)
            )

            self.fields["entities"].queryset = TransactionEntity.objects.filter(
                Q(active=True) | Q(transactions=self.instance.id)
            )
        else:
            self.fields["account"].queryset = Account.objects.filter(
                is_archived=False,
            )
            user_settings = get_current_user().settings
            if user_settings.default_account:
                self.fields["account"].initial = user_settings.default_account

            self.fields["category"].queryset = TransactionCategory.objects.filter(
                active=True
            )
            self.fields["tags"].queryset = TransactionTag.objects.filter(active=True)
            self.fields["entities"].queryset = TransactionEntity.objects.all()

        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.form_method = "post"
        self.helper.layout = Layout(
            Field(
                "type",
                template="transactions/widgets/income_expense_toggle_buttons.html",
            ),
            Field("is_paid", template="transactions/widgets/paid_toggle_button.html"),
            Row(
                Column("account"),
                Column("entities"),
            ),
            Row(
                Column(Field("date")),
                Column(Field("reference_date")),
            ),
            "description",
            Field("amount", inputmode="decimal"),
            Row(
                Column("category"),
                Column("tags"),
            ),
            "notes",
        )

        self.helper_simple = FormHelper()
        self.helper_simple.form_tag = False
        self.helper_simple.form_method = "post"
        self.helper_simple.layout = Layout(
            Field(
                "type",
                template="transactions/widgets/income_expense_toggle_buttons.html",
            ),
            Field("is_paid", template="transactions/widgets/paid_toggle_button.html"),
            "account",
            Row(
                Column(Field("date")),
                Column(Field("reference_date")),
            ),
            "description",
            Field("amount", inputmode="decimal"),
            Accordion(
                AccordionGroup(
                    _("More"),
                    "entities",
                    Row(
                        Column("category"),
                        Column("tags"),
                    ),
                    "notes",
                    active=False,
                ),
                flush=False,
                always_open=False,
                css_class="mb-3",
            ),
            FormActions(
                NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
            ),
        )

        self.fields["date"].widget = AirDatePickerInput(clear_button=False)

        if self.instance and self.instance.pk:
            decimal_places = self.instance.account.currency.decimal_places
            self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput(
                decimal_places=decimal_places
            )
            self.helper.layout.append(
                FormActions(
                    NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
                ),
            )
        else:
            self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
            self.helper.layout.append(
                Div(
                    NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
                    NoClassSubmit(
                        "submit_and_similar",
                        _("Save and add similar"),
                        css_class="btn btn-primary btn-soft",
                    ),
                    NoClassSubmit(
                        "submit_and_another",
                        _("Save and add another"),
                        css_class="btn btn-primary btn-soft",
                    ),
                    css_class="flex flex-col gap-2 mt-3",
                ),
            )

    def clean(self):
        cleaned_data = super().clean()
        date = cleaned_data.get("date")
        reference_date = cleaned_data.get("reference_date")

        if date and not reference_date:
            cleaned_data["reference_date"] = date.replace(day=1)

        return cleaned_data

    def save(self, **kwargs):
        is_new = not self.instance.id

        if not is_new:
            old_data = deepcopy(Transaction.objects.get(pk=self.instance.id))
        else:
            old_data = None

        instance = super().save(**kwargs)
        if is_new:
            transaction_created.send(sender=instance)
        else:
            transaction_updated.send(sender=instance, old_data=old_data)

        return instance


class TransactionAttachmentForm(forms.Form):
    attachments = MultipleFileField(
        required=True,
        label=_("Attachments"),
        help_text=_("Files are private and only visible to users with access to this transaction."),
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.form_method = "post"
        self.helper.layout = Layout(
            "attachments",
            FormActions(
                NoClassSubmit("submit", _("Upload"), css_class="btn btn-primary"),
            ),
        )

    def save(self, transaction, uploaded_by):
        created = []
        for attachment in self.cleaned_data.get("attachments") or []:
            created.append(
                TransactionAttachment.objects.create(
                    transaction=transaction,
                    file=attachment,
                    original_name=attachment.name,
                    content_type=getattr(attachment, "content_type", ""),
                    size=attachment.size,
                    uploaded_by=uploaded_by,
                )
            )
        return created


class QuickTransactionForm(forms.ModelForm):
    category = DynamicModelChoiceField(
        create_field="name",
        model=TransactionCategory,
        required=False,
        label=_("Category"),
        queryset=TransactionCategory.objects.filter(active=True),
    )
    tags = DynamicModelMultipleChoiceField(
        model=TransactionTag,
        to_field_name="name",
        create_field="name",
        required=False,
        label=_("Tags"),
        queryset=TransactionTag.objects.filter(active=True),
    )
    entities = DynamicModelMultipleChoiceField(
        model=TransactionEntity,
        to_field_name="name",
        create_field="name",
        required=False,
        label=_("Entities"),
    )
    account = forms.ModelChoiceField(
        queryset=Account.objects.filter(is_archived=False),
        label=_("Account"),
        widget=TomSelect(clear_button=False, group_by="group"),
    )

    class Meta:
        model = QuickTransaction
        fields = [
            "name",
            "account",
            "type",
            "is_paid",
            "amount",
            "description",
            "notes",
            "category",
            "tags",
            "entities",
            "mute",
        ]
        widgets = {
            "notes": forms.Textarea(attrs={"rows": 3}),
            "account": TomSelect(clear_button=False, group_by="group"),
        }
        help_texts = {
            "mute": _("Muted transactions won't be displayed on monthly summaries")
        }

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

        # if editing a transaction display non-archived items and it's own item even if it's archived
        if self.instance.id:
            self.fields["account"].queryset = Account.objects.filter(
                Q(is_archived=False) | Q(transactions=self.instance.id),
            )

            self.fields["category"].queryset = TransactionCategory.objects.filter(
                Q(active=True) | Q(transaction=self.instance.id)
            )

            self.fields["tags"].queryset = TransactionTag.objects.filter(
                Q(active=True) | Q(transaction=self.instance.id)
            )

            self.fields["entities"].queryset = TransactionEntity.objects.filter(
                Q(active=True) | Q(transactions=self.instance.id)
            )
        else:
            self.fields["account"].queryset = Account.objects.filter(
                is_archived=False,
            )

            self.fields["category"].queryset = TransactionCategory.objects.filter(
                active=True
            )
            self.fields["tags"].queryset = TransactionTag.objects.filter(active=True)
            self.fields["entities"].queryset = TransactionEntity.objects.all()

        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.form_method = "post"
        self.helper.layout = Layout(
            Field(
                "type",
                template="transactions/widgets/income_expense_toggle_buttons.html",
            ),
            Field("is_paid", template="transactions/widgets/paid_toggle_button.html"),
            "name",
            HTML('<hr class="hr my-3" />'),
            Row(
                Column("account"),
                Column("entities"),
            ),
            "description",
            Field("amount", inputmode="decimal"),
            Row(
                Column("category"),
                Column("tags"),
            ),
            "notes",
            Switch("mute"),
        )

        if self.instance and self.instance.pk:
            decimal_places = self.instance.account.currency.decimal_places
            self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput(
                decimal_places=decimal_places
            )
            self.helper.layout.append(
                FormActions(
                    NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
                ),
            )
        else:
            self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
            self.helper.layout.append(
                FormActions(
                    NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
                ),
            )


class BulkEditTransactionForm(forms.Form):
    type = forms.ChoiceField(
        choices=(Transaction.Type.choices),
        required=False,
        label=_("Type"),
    )
    is_paid = forms.NullBooleanField(
        required=False,
        label=_("Paid"),
    )
    account = DynamicModelChoiceField(
        model=Account,
        required=False,
        label=_("Account"),
        queryset=Account.objects.filter(is_archived=False),
        widget=TomSelect(clear_button=False, group_by="group"),
    )
    date = forms.DateField(
        label=_("Date"),
        required=False,
        widget=AirDatePickerInput(clear_button=False),
    )
    reference_date = forms.DateField(
        widget=AirMonthYearPickerInput(),
        label=_("Reference Date"),
        required=False,
    )
    amount = forms.DecimalField(
        max_digits=42,
        decimal_places=30,
        required=False,
        label=_("Amount"),
        widget=ArbitraryDecimalDisplayNumberInput(),
    )
    description = forms.CharField(
        max_length=500, required=False, label=_("Description")
    )
    notes = forms.CharField(
        required=False,
        widget=forms.Textarea(attrs={"rows": 3}),
        label=_("Notes"),
    )
    category = DynamicModelChoiceField(
        create_field="name",
        model=TransactionCategory,
        required=False,
        label=_("Category"),
        queryset=TransactionCategory.objects.filter(active=True),
    )
    tags = DynamicModelMultipleChoiceField(
        model=TransactionTag,
        to_field_name="name",
        create_field="name",
        required=False,
        label=_("Tags"),
        queryset=TransactionTag.objects.filter(active=True),
    )
    entities = DynamicModelMultipleChoiceField(
        model=TransactionEntity,
        to_field_name="name",
        create_field="name",
        required=False,
        label=_("Entities"),
        queryset=TransactionEntity.objects.all(),
    )

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

        self.fields["account"].queryset = Account.objects.filter(
            is_archived=False,
        )

        self.fields["category"].queryset = TransactionCategory.objects.filter(
            active=True
        )
        self.fields["tags"].queryset = TransactionTag.objects.filter(active=True)
        self.fields["entities"].queryset = TransactionEntity.objects.all()

        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.form_method = "post"
        self.helper.layout = Layout(
            Field(
                "type",
                template="transactions/widgets/unselectable_income_expense_toggle_buttons.html",
            ),
            Field(
                "is_paid",
                template="transactions/widgets/unselectable_paid_toggle_button.html",
            ),
            Row(
                Column("account"),
                Column("entities"),
            ),
            Row(
                Column(Field("date")),
                Column(Field("reference_date")),
            ),
            "description",
            Field("amount", inputmode="decimal"),
            Row(
                Column("category"),
                Column("tags"),
            ),
            "notes",
            FormActions(
                NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
            ),
        )

        self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
        self.fields["date"].widget = AirDatePickerInput(clear_button=False)


class TransferForm(forms.Form):
    from_account = forms.ModelChoiceField(
        queryset=Account.objects.filter(is_archived=False),
        label=_("From Account"),
        widget=TomSelect(clear_button=False, group_by="group"),
    )
    to_account = forms.ModelChoiceField(
        queryset=Account.objects.filter(is_archived=False),
        label=_("To Account"),
        widget=TomSelect(clear_button=False, group_by="group"),
    )

    from_amount = forms.DecimalField(
        max_digits=42,
        decimal_places=30,
        label=_("From Amount"),
    )
    to_amount = forms.DecimalField(
        max_digits=42,
        decimal_places=30,
        label=_("To Amount"),
        required=False,
    )

    from_category = DynamicModelChoiceField(
        create_field="name",
        model=TransactionCategory,
        required=False,
        label=_("Category"),
        queryset=TransactionCategory.objects.filter(active=True),
    )
    to_category = DynamicModelChoiceField(
        create_field="name",
        model=TransactionCategory,
        required=False,
        label=_("Category"),
        queryset=TransactionCategory.objects.filter(active=True),
    )

    from_tags = DynamicModelMultipleChoiceField(
        model=TransactionTag,
        to_field_name="name",
        create_field="name",
        required=False,
        label=_("Tags"),
        queryset=TransactionTag.objects.filter(active=True),
    )
    to_tags = DynamicModelMultipleChoiceField(
        model=TransactionTag,
        to_field_name="name",
        create_field="name",
        required=False,
        label=_("Tags"),
        queryset=TransactionTag.objects.filter(active=True),
    )

    date = forms.DateField(label=_("Date"))

    reference_date = forms.DateField(
        widget=AirMonthYearPickerInput(), label=_("Reference Date"), required=False
    )

    description = forms.CharField(
        max_length=500, label=_("Description"), required=False
    )
    notes = forms.CharField(
        required=False,
        widget=forms.Textarea(
            attrs={
                "rows": 3,
            }
        ),
        label=_("Notes"),
    )

    mute = forms.BooleanField(
        label=_("Mute"),
        initial=True,
        required=False,
        help_text=_("Muted transactions won't be displayed on monthly summaries"),
    )

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

        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.form_method = "post"

        self.helper.layout = Layout(
            Row(
                Column(Field("date")),
                Column(
                    Field("reference_date"),
                ),
            ),
            Field("description"),
            Field("notes"),
            Switch("mute"),
            Row(
                Column("from_account"),
                Column(Field("from_amount")),
                Column("from_category"),
                Column("from_tags"),
                css_class="bg-base-100 rounded-box p-4 border-base-content/60 border my-3",
            ),
            Row(
                Column(
                    "to_account",
                ),
                Column(
                    Field("to_amount"),
                ),
                Column("to_category"),
                Column("to_tags"),
                css_class="bg-base-100 rounded-box p-4 border-base-content/60 border",
            ),
            FormActions(
                NoClassSubmit("submit", _("Transfer"), css_class="btn btn-primary"),
            ),
        )

        self.fields["from_amount"].widget = ArbitraryDecimalDisplayNumberInput()
        self.fields["to_amount"].widget = ArbitraryDecimalDisplayNumberInput()
        self.fields["date"].widget = AirDatePickerInput(clear_button=False)

        self.fields["from_account"].queryset = Account.objects.filter(
            is_archived=False,
        )

        self.fields["from_category"].queryset = TransactionCategory.objects.filter(
            active=True
        )
        self.fields["from_tags"].queryset = TransactionTag.objects.filter(active=True)

        self.fields["to_account"].queryset = Account.objects.filter(
            is_archived=False,
        )

        self.fields["to_category"].queryset = TransactionCategory.objects.filter(
            active=True
        )
        self.fields["to_tags"].queryset = TransactionTag.objects.filter(active=True)

    def clean(self):
        cleaned_data = super().clean()
        from_account = cleaned_data.get("from_account")
        to_account = cleaned_data.get("to_account")

        if from_account == to_account:
            raise forms.ValidationError(_("From and To accounts must be different."))

        return cleaned_data

    def save(self):
        mute = self.cleaned_data["mute"]

        from_account = self.cleaned_data["from_account"]
        to_account = self.cleaned_data["to_account"]
        from_amount = self.cleaned_data["from_amount"]
        to_amount = self.cleaned_data["to_amount"] or from_amount
        date = self.cleaned_data["date"]
        reference_date = self.cleaned_data["reference_date"]
        description = self.cleaned_data["description"]
        from_category = self.cleaned_data.get("from_category")
        to_category = self.cleaned_data.get("to_category")
        notes = self.cleaned_data.get("notes")

        # Create "From" transaction
        from_transaction = Transaction.objects.create(
            account=from_account,
            type=Transaction.Type.EXPENSE,
            is_paid=True,
            date=date,
            reference_date=reference_date,
            amount=from_amount,
            description=description,
            category=from_category,
            notes=notes,
            mute=mute,
        )
        from_transaction.tags.set(self.cleaned_data.get("from_tags", []))

        # Create "To" transaction
        to_transaction = Transaction.objects.create(
            account=to_account,
            type=Transaction.Type.INCOME,
            is_paid=True,
            date=date,
            reference_date=reference_date,
            amount=to_amount,
            description=description,
            category=to_category,
            notes=notes,
            mute=mute,
        )
        to_transaction.tags.set(self.cleaned_data.get("to_tags", []))

        return from_transaction, to_transaction


class InstallmentPlanForm(forms.ModelForm):
    account = forms.ModelChoiceField(
        queryset=Account.objects.filter(is_archived=False),
        label=_("Account"),
        widget=TomSelect(clear_button=False, group_by="group"),
    )
    tags = DynamicModelMultipleChoiceField(
        model=TransactionTag,
        to_field_name="name",
        create_field="name",
        required=False,
        label=_("Tags"),
        queryset=TransactionTag.objects.filter(active=True),
    )
    category = DynamicModelChoiceField(
        create_field="name",
        model=TransactionCategory,
        required=False,
        label=_("Category"),
        queryset=TransactionCategory.objects.filter(active=True),
    )
    entities = DynamicModelMultipleChoiceField(
        model=TransactionEntity,
        to_field_name="name",
        create_field="name",
        required=False,
        label=_("Entities"),
        queryset=TransactionEntity.objects.filter(active=True),
    )
    type = forms.ChoiceField(choices=Transaction.Type.choices)

    reference_date = forms.DateField(
        widget=AirMonthYearPickerInput(), label=_("Reference Date"), required=False
    )

    class Meta:
        model = InstallmentPlan
        fields = [
            "type",
            "account",
            "start_date",
            "reference_date",
            "description",
            "number_of_installments",
            "recurrence",
            "installment_amount",
            "category",
            "tags",
            "notes",
            "installment_start",
            "entities",
            "add_description_to_transaction",
            "add_notes_to_transaction",
        ]
        widgets = {
            "account": TomSelect(),
            "recurrence": TomSelect(clear_button=False),
            "notes": forms.Textarea(attrs={"rows": 3}),
        }

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

        # if editing display non-archived items and it's own item even if it's archived
        if self.instance.id:
            self.fields["account"].queryset = Account.objects.filter(
                Q(is_archived=False) | Q(installmentplan=self.instance.id)
            ).distinct()

            self.fields["category"].queryset = TransactionCategory.objects.filter(
                Q(active=True) | Q(installmentplan=self.instance.id)
            ).distinct()

            self.fields["tags"].queryset = TransactionTag.objects.filter(
                Q(active=True) | Q(installmentplan=self.instance.id)
            ).distinct()

            self.fields["entities"].queryset = TransactionEntity.objects.filter(
                Q(active=True) | Q(installmentplan=self.instance.id)
            ).distinct()
        else:
            self.fields["account"].queryset = Account.objects.filter(is_archived=False)
            user_settings = get_current_user().settings
            if user_settings.default_account:
                self.fields["account"].initial = user_settings.default_account

            self.fields["category"].queryset = TransactionCategory.objects.filter(
                active=True
            )

            self.fields["tags"].queryset = TransactionTag.objects.filter(active=True)

            self.fields["entities"].queryset = TransactionEntity.objects.filter(
                active=True
            )

        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.form_method = "post"

        self.helper.layout = Layout(
            Field(
                "type",
                template="transactions/widgets/income_expense_toggle_buttons.html",
            ),
            Row(
                Column("account"),
                Column("entities"),
            ),
            "description",
            Switch("add_description_to_transaction"),
            "notes",
            Switch("add_notes_to_transaction"),
            Row(
                Column("number_of_installments"),
                Column("installment_start"),
            ),
            Row(
                Column("start_date", css_class="col-span-12 md:col-span-4"),
                Column("reference_date", css_class="col-span-12 md:col-span-4"),
                Column("recurrence", css_class="col-span-12 md:col-span-4"),
            ),
            "installment_amount",
            Row(
                Column("category"),
                Column("tags"),
            ),
        )

        self.fields["installment_amount"].widget = ArbitraryDecimalDisplayNumberInput()
        self.fields["start_date"].widget = AirDatePickerInput(clear_button=False)

        if self.instance and self.instance.pk:
            self.helper.layout.append(
                FormActions(
                    NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
                ),
            )
        else:
            self.helper.layout.append(
                FormActions(
                    NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
                ),
            )

    def save(self, **kwargs):
        is_new = not self.instance.id

        instance = super().save(**kwargs)
        if is_new:
            instance.create_transactions()
        else:
            instance.update_transactions()

        return instance


class TransactionTagForm(forms.ModelForm):
    class Meta:
        model = TransactionTag
        fields = ["name", "active"]
        labels = {"name": _("Tag name")}

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

        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.form_method = "post"
        self.helper.layout = Layout(Field("name", css_class="mb-3"), Switch("active"))

        if self.instance and self.instance.pk:
            self.helper.layout.append(
                FormActions(
                    NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
                ),
            )
        else:
            self.helper.layout.append(
                FormActions(
                    NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
                ),
            )


class TransactionEntityForm(forms.ModelForm):
    class Meta:
        model = TransactionEntity
        fields = ["name", "active"]
        labels = {"name": _("Entity name")}

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

        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.form_method = "post"
        self.helper.layout = Layout(Field("name"), Switch("active"))

        if self.instance and self.instance.pk:
            self.helper.layout.append(
                FormActions(
                    NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
                ),
            )
        else:
            self.helper.layout.append(
                FormActions(
                    NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
                ),
            )


class TransactionCategoryForm(forms.ModelForm):
    class Meta:
        model = TransactionCategory
        fields = ["name", "mute", "active"]
        labels = {"name": _("Category name")}
        help_texts = {
            "mute": _("Muted categories won't be displayed on monthly summaries")
        }

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

        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.form_method = "post"
        self.helper.layout = Layout(Field("name"), Switch("mute"), Switch("active"))

        if self.instance and self.instance.pk:
            self.helper.layout.append(
                FormActions(
                    NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
                ),
            )
        else:
            self.helper.layout.append(
                FormActions(
                    NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
                ),
            )


class RecurringTransactionForm(forms.ModelForm):
    account = forms.ModelChoiceField(
        queryset=Account.objects.filter(is_archived=False),
        label=_("Account"),
        widget=TomSelect(clear_button=False, group_by="group"),
    )
    tags = DynamicModelMultipleChoiceField(
        model=TransactionTag,
        to_field_name="name",
        create_field="name",
        required=False,
        label=_("Tags"),
        queryset=TransactionTag.objects.filter(active=True),
    )
    category = DynamicModelChoiceField(
        create_field="name",
        model=TransactionCategory,
        required=False,
        label=_("Category"),
        queryset=TransactionCategory.objects.filter(active=True),
    )
    entities = DynamicModelMultipleChoiceField(
        model=TransactionEntity,
        to_field_name="name",
        create_field="name",
        required=False,
        label=_("Entities"),
        queryset=TransactionEntity.objects.filter(active=True),
    )
    type = forms.ChoiceField(choices=Transaction.Type.choices)

    class Meta:
        model = RecurringTransaction
        fields = [
            "account",
            "type",
            "amount",
            "description",
            "add_description_to_transaction",
            "category",
            "tags",
            "start_date",
            "reference_date",
            "end_date",
            "recurrence_type",
            "recurrence_interval",
            "notes",
            "add_notes_to_transaction",
            "entities",
            "keep_at_most",
        ]
        widgets = {
            "reference_date": AirMonthYearPickerInput(),
            "recurrence_type": TomSelect(clear_button=False),
            "notes": forms.Textarea(
                attrs={
                    "rows": 3,
                }
            ),
        }

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

        # if editing display non-archived items and it's own item even if it's archived
        if self.instance.id:
            self.fields["account"].queryset = Account.objects.filter(
                Q(is_archived=False) | Q(recurringtransaction=self.instance.id)
            ).distinct()

            self.fields["category"].queryset = TransactionCategory.objects.filter(
                Q(active=True) | Q(recurringtransaction=self.instance.id)
            ).distinct()

            self.fields["tags"].queryset = TransactionTag.objects.filter(
                Q(active=True) | Q(recurringtransaction=self.instance.id)
            ).distinct()

            self.fields["entities"].queryset = TransactionEntity.objects.filter(
                Q(active=True) | Q(recurringtransaction=self.instance.id)
            ).distinct()
        else:
            self.fields["account"].queryset = Account.objects.filter(is_archived=False)
            
            user_settings = get_current_user().settings
            if user_settings.default_account:
                self.fields["account"].initial = user_settings.default_account

            self.fields["category"].queryset = TransactionCategory.objects.filter(
                active=True
            )

            self.fields["tags"].queryset = TransactionTag.objects.filter(active=True)

            self.fields["entities"].queryset = TransactionEntity.objects.filter(
                active=True
            )

        self.helper = FormHelper()
        self.helper.form_method = "post"
        self.helper.form_tag = False

        self.helper.layout = Layout(
            Field(
                "type",
                template="transactions/widgets/income_expense_toggle_buttons.html",
            ),
            Row(
                Column("account"),
                Column("entities"),
            ),
            "description",
            Switch("add_description_to_transaction"),
            "amount",
            Row(
                Column("category"),
                Column("tags"),
            ),
            "notes",
            Switch("add_notes_to_transaction"),
            Row(
                Column("start_date"),
                Column("reference_date"),
            ),
            Row(
                Column("recurrence_interval", css_class="col-span-12 md:col-span-4"),
                Column("recurrence_type", css_class="col-span-12 md:col-span-4"),
                Column("end_date", css_class="col-span-12 md:col-span-4"),
            ),
            AppendedText("keep_at_most", _("future transactions")),
        )

        self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
        self.fields["start_date"].widget = AirDatePickerInput(clear_button=False)
        self.fields["end_date"].widget = AirDatePickerInput()

        if self.instance and self.instance.pk:
            self.helper.layout.append(
                FormActions(
                    NoClassSubmit("submit", _("Update"), css_class="btn btn-primary"),
                ),
            )
        else:
            self.helper.layout.append(
                FormActions(
                    NoClassSubmit("submit", _("Add"), css_class="btn btn-primary"),
                ),
            )

    def clean(self):
        cleaned_data = super().clean()
        start_date = cleaned_data.get("start_date")
        end_date = cleaned_data.get("end_date")

        if start_date and end_date and start_date > end_date:
            raise forms.ValidationError(_("End date should be after the start date"))

        return cleaned_data

    def save(self, **kwargs):
        is_new = not self.instance.id

        instance = super().save(**kwargs)
        if is_new:
            instance.create_upcoming_transactions()
        else:
            instance.update_unpaid_transactions()
            instance.generate_upcoming_transactions()

        return instance
