"""
horilla_views/forms.py
"""

import os

from django import forms
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.db import transaction
from django.template.loader import render_to_string
from django.utils.safestring import SafeText
from django.utils.translation import gettext_lazy as _trans

from horilla.horilla_middlewares import _thread_locals
from horilla_views import models
from horilla_views.cbv_methods import (
    BOOLEAN_CHOICES,
    FIELD_WIDGET_MAP,
    MODEL_FORM_FIELD_MAP,
    get_field_class_map,
    structured,
)
from horilla_views.templatetags.generic_template_filters import getattribute


class ToggleColumnForm(forms.Form):
    """
    Toggle column form
    """

    def __init__(self, columns, hidden_fields: list, *args, **kwargs):
        request = getattr(_thread_locals, "request", {})
        self.request = request
        super().__init__(*args, **kwargs)
        for column in columns:
            initial = True
            if column[1] in hidden_fields:
                initial = False

            self.fields[column[1]] = forms.BooleanField(
                label=column[0], initial=initial
            )

    def as_list(self) -> SafeText:
        """
        Render the form fields as HTML table rows with.
        """
        context = {"form": self, "request": self.request}
        table_html = render_to_string("generic/as_list.html", context)
        return table_html


class SavedFilterForm(forms.ModelForm):
    """
    SavedFilterForm
    """

    color = forms.CharField(
        widget=forms.TextInput(
            attrs={
                "class": "oh-input w-100",
                "type": "color",
                "placeholder": "Choose a color",
            }
        )
    )

    class Meta:
        model = models.SavedFilter
        fields = ["title", "is_default", "color"]

    def structured(self):
        """
        Render the form fields as HTML table rows with Bootstrap styling.
        """
        request = getattr(_thread_locals, "request", None)
        context = {
            "form": self,
            "request": request,
        }
        table_html = render_to_string("common_form.html", context)
        return table_html

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        attrs = self.fields["title"].widget.attrs
        attrs["class"] = "oh-input w-100"
        attrs["placeholder"] = "Saved filter title"
        if self.instance.pk:
            self.verbose_name = self.instance.title


class DynamicBulkUpdateForm(forms.Form):
    """
    DynamicBulkUpdateForm
    """

    verbose_name = _trans("Bulk Update")

    def __init__(
        self,
        *args,
        root_model: models.models.Model = None,
        bulk_update_fields: list = [],
        ids: list = [],
        **kwargs,
    ):
        self.ids = ids
        self.root_model = root_model
        self.bulk_update_fields = sorted(
            bulk_update_fields, key=lambda x: x.count("__")
        )
        self.structured = structured
        mappings = get_field_class_map(root_model, bulk_update_fields)
        self.request = getattribute(_thread_locals, "request")

        super().__init__(*args, **kwargs)
        for key, val in mappings.items():
            widget = FIELD_WIDGET_MAP.get(type(val))
            field = MODEL_FORM_FIELD_MAP.get(type(val))
            if widget and field:
                if isinstance(val, models.models.BooleanField):
                    self.fields[key] = forms.ChoiceField(
                        choices=BOOLEAN_CHOICES,
                        widget=widget,
                        label=val.verbose_name.capitalize(),
                        required=False,
                    )
                    self.fields[key].widget.option_template_name = (
                        "horilla_widgets/select_option.html",
                    )
                    continue
                elif not getattribute(val, "related_model"):
                    if isinstance(val, models.models.CharField) and val.choices:
                        self.fields[key] = forms.ChoiceField(
                            choices=[("", "--------")]
                            + [choice for choice in val.choices if choice[0] != ""],
                            widget=forms.Select(
                                attrs={"class": "oh-select oh-select-2 w-100"}
                            ),
                            label=val.verbose_name.capitalize(),
                            required=False,
                        )
                        self.fields[key].widget.option_template_name = (
                            "horilla_widgets/select_option.html",
                        )
                        continue
                    self.fields[key] = field(
                        widget=widget,
                        label=val.verbose_name.capitalize(),
                        required=False,
                    )
                    self.fields[key].widget.option_template_name = (
                        "horilla_widgets/select_option.html",
                    )
                    continue
                queryset = val.related_model.objects.all()
                self.fields[key] = field(
                    widget=widget,
                    queryset=queryset,
                    label=val.verbose_name,
                    required=False,
                )
                self.fields[key].widget.option_template_name = (
                    "horilla_widgets/select_option.html",
                )

    def is_valid(self):
        valid = True
        try:
            with transaction.atomic():
                # Perform bulk update
                self.save()
                # Simulate error check
                raise Exception("no_errors")
        except Exception as e:
            # Handle errors or validation issues
            if not "no_errors" in str(e):
                valid = False
                self.add_error(None, f"Form not valid: {str(e)}")
        return valid

    def save(self, *args, **kwargs):
        """
        Bulk save method
        """
        mappings = get_field_class_map(self.root_model, self.bulk_update_fields)
        data_mapp = {}
        data_m2m_mapp = {}
        relation_mapp = {}
        map_queryset = {}
        fiels_mapping = {}
        parent_model = self.root_model
        for key, val in mappings.items():
            field = MODEL_FORM_FIELD_MAP.get(type(val))
            if field:
                if not fiels_mapping.get(val.model):
                    fiels_mapping[val.model] = {}
                if not data_m2m_mapp.get(val.model):
                    data_m2m_mapp[val.model] = {}
                if not data_mapp.get(val.model):
                    data_mapp[val.model] = {}
                    if not relation_mapp.get(val.model):
                        if val.model == self.root_model:
                            relation_mapp[val.model] = "id__in"
                        else:
                            related_key = key.split("__")[-2]
                            field = getattribute(parent_model, related_key)
                            relation_mapp[val.model] = (
                                field.related.field.name
                                + "__"
                                + relation_mapp[parent_model]
                            )
                            parent_model = val.model
                files = self.files.getlist(key)
                value = self.data.getlist(key)
                if (not value or not value[0]) and not files:
                    continue
                key = key.split("__")[-1]
                model_field = getattribute(val.model, key).field
                if isinstance(model_field, models.models.ManyToManyField):
                    data_m2m_mapp[val.model][key] = value
                    continue
                if files and isinstance(
                    model_field,
                    (
                        models.models.FileField,
                        models.models.ImageField,
                    ),
                ):
                    file_path = os.path.join(model_field.upload_to, files[0].name)

                    data_mapp[val.model][key] = file_path
                    fiels_mapping[val.model][model_field] = files[0]
                    continue
                data_mapp[val.model][key] = value[0]

        for model, data in data_mapp.items():
            queryset = model.objects.filter(**{relation_mapp[model]: self.ids})
            # here fields, files, and related fields-
            # get updated but need to save the files manually
            queryset.update(**data)
            map_queryset[model] = queryset
            m2m_data = data_m2m_mapp[model]
            # saving m2m
            if m2m_data:
                for field, ids in m2m_data.items():
                    related_objects = getattr(
                        model, field
                    ).field.related_model.objects.filter(id__in=ids)
                    for instance in queryset:
                        getattr(instance, field).set(related_objects)
        for model, files in fiels_mapping.items():
            if files:
                for field, file in files.items():
                    file_path = os.path.join(field.upload_to, file.name)
                    default_storage.save(file_path, ContentFile(file.read()))
