import json

from django import forms
from django.conf import settings
from django.forms.models import modelform_factory
from django.utils.text import capfirst
from django.utils.translation import gettext as _

from wagtail.admin.forms.collections import (
    BaseCollectionMemberForm,
    CollectionChoiceField,
    collection_member_permission_formset_factory,
)
from wagtail.admin.forms.tags import validate_tag_length
from wagtail.admin.widgets import AdminTagWidget
from wagtail.images.fields import WagtailImageField
from wagtail.images.formats import get_image_formats
from wagtail.images.models import Image
from wagtail.images.permissions import permission_policy as images_permission_policy
from wagtail.models import Collection
from wagtail.search import index as search_index


# Callback to allow us to override the default form field for the image file field and collection field.
def formfield_for_dbfield(db_field, **kwargs):
    # Check if this is the file field
    if db_field.name == "file":
        return WagtailImageField(label=capfirst(db_field.verbose_name), **kwargs)
    elif db_field.name == "collection":
        return CollectionChoiceField(
            label=_("Collection"),
            queryset=Collection.objects.all(),
            empty_label=None,
            **kwargs,
        )

    # For all other fields, just call its formfield() method.
    return db_field.formfield(**kwargs)


class BaseImageForm(BaseCollectionMemberForm):
    permission_policy = images_permission_policy

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.original_file = self.instance.file
        # Dynamically set sync value target to the id of the file input rather
        # than hardcoding it
        if "title" in self.fields and "file" in self.fields:
            self.fields["file"].widget.attrs["data-w-sync-target-value"] = (
                f"#{self['title'].id_for_label}"
            )

    def save(self, commit=True):
        if "file" in self.changed_data:
            self.instance._set_image_file_metadata()

        super().save(commit=commit)

        if commit:
            if "file" in self.changed_data and self.original_file:
                # if providing a new image file, delete the old one and all renditions.
                # NB Doing this via original_file.delete() clears the file field,
                # which definitely isn't what we want...
                self.original_file.storage.delete(self.original_file.name)
                self.instance.renditions.all().delete()

            # Reindex the image to make sure all tags are indexed
            search_index.insert_or_update_object(self.instance)

        return self.instance

    class Meta:
        # set the 'file' widget to a FileInput rather than the default ClearableFileInput
        # so that when editing, we don't get the 'currently: ...' banner which is
        # a bit pointless here
        widgets = {
            "tags": AdminTagWidget,
            "file": forms.FileInput(
                attrs={
                    "data-controller": "w-sync",
                    "data-action": "input->w-sync#apply",
                    "data-w-sync-bubbles-param": "true",
                    "data-w-sync-name-value": "wagtail:images-upload",
                    "data-w-sync-normalize-value": "true",
                }
            ),
            "focal_point_x": forms.HiddenInput(attrs={"class": "focal_point_x"}),
            "focal_point_y": forms.HiddenInput(attrs={"class": "focal_point_y"}),
            "focal_point_width": forms.HiddenInput(
                attrs={"class": "focal_point_width"}
            ),
            "focal_point_height": forms.HiddenInput(
                attrs={"class": "focal_point_height"}
            ),
        }

    def clean_tags(self):
        tags = self.cleaned_data["tags"]
        validate_tag_length(tags)
        return tags


def get_image_base_form():
    base_form_override = getattr(settings, "WAGTAILIMAGES_IMAGE_FORM_BASE", "")
    if base_form_override:
        from django.utils.module_loading import import_string

        base_form = import_string(base_form_override)
    else:
        base_form = BaseImageForm
    return base_form


def get_image_form(model):
    fields = model.admin_form_fields
    if "collection" not in fields:
        # force addition of the 'collection' field, because leaving it out can
        # cause dubious results when multiple collections exist (e.g adding the
        # document to the root collection where the user may not have permission) -
        # and when only one collection exists, it will get hidden anyway.
        fields = list(fields) + ["collection"]

    BaseForm = get_image_base_form()

    # If the base form specifies the 'tags' widget as a plain unconfigured AdminTagWidget,
    # substitute one that correctly passes the tag model used on the image model.
    # (If the widget has been overridden via WAGTAILIMAGES_IMAGE_FORM_BASE, leave it
    # alone and trust that they know what they're doing)
    widgets = None
    if BaseForm._meta.widgets.get("tags") == AdminTagWidget:
        tag_model = model._meta.get_field("tags").related_model
        widgets = BaseForm._meta.widgets.copy()
        widgets["tags"] = AdminTagWidget(tag_model=tag_model)

    return modelform_factory(
        model,
        form=BaseForm,
        fields=fields,
        widgets=widgets,
        formfield_callback=formfield_for_dbfield,
    )


def get_image_multi_form(model_class):
    # edit form for use within the multiple uploader
    ImageForm = get_image_form(model_class)

    # Make a new form with the file and focal point fields excluded
    class ImageEditForm(ImageForm):
        class Meta(ImageForm.Meta):
            model = model_class
            exclude = (
                "file",
                "focal_point_x",
                "focal_point_y",
                "focal_point_width",
                "focal_point_height",
            )

    return ImageEditForm


class ImageInsertionForm(forms.Form):
    """
    Form for selecting parameters of the image (e.g. format) prior to insertion
    into a rich text area
    """

    format = forms.ChoiceField(
        label=_("Format"),
        choices=[(format.name, format.label) for format in get_image_formats()],
        widget=forms.RadioSelect,
    )
    image_is_decorative = forms.BooleanField(
        required=False, label=_("Image is decorative")
    )
    alt_text = forms.CharField(required=False, label=_("Alt text"))

    def clean_alt_text(self):
        alt_text = self.cleaned_data["alt_text"]
        image_is_decorative = self.cleaned_data["image_is_decorative"]

        # Empty the alt text value if the image is set to be decorative
        if image_is_decorative:
            return ""
        else:
            # Alt text is required if image is not decorative.
            if not alt_text:
                msg = _(
                    "Please add some alt text for your image or mark it as decorative"
                )
                self.add_error("alt_text", msg)
        return alt_text


class URLGeneratorForm(forms.Form):
    filter_method = forms.ChoiceField(
        label=_("Filter"),
        choices=(
            ("original", _("Original size")),
            ("width", _("Resize to width")),
            ("height", _("Resize to height")),
            ("min", _("Resize to min")),
            ("max", _("Resize to max")),
            ("fill", _("Resize to fill")),
        ),
    )
    width = forms.IntegerField(
        label=_("Width"),
        min_value=0,
        widget=forms.NumberInput(
            attrs={
                "disabled": True,
                "data-w-rules-target": "enable",
                "data-w-rules": json.dumps(
                    {"filter_method": ["width", "max", "min", "fill"]}
                ),
            },
        ),
    )
    height = forms.IntegerField(
        label=_("Height"),
        min_value=0,
        widget=forms.NumberInput(
            attrs={
                "disabled": True,
                "data-w-rules-target": "enable",
                "data-w-rules": json.dumps(
                    {"filter_method": ["height", "max", "min", "fill"]}
                ),
            }
        ),
    )
    closeness = forms.IntegerField(
        label=_("Closeness"),
        min_value=0,
        max_value=100,
        initial=0,
        widget=forms.NumberInput(
            attrs={
                "disabled": True,
                "data-w-rules-target": "enable",
                "data-w-rules": json.dumps({"filter_method": ["fill"]}),
            }
        ),
    )


GroupImagePermissionFormSet = collection_member_permission_formset_factory(
    Image,
    [
        ("add_image", _("Add"), _("Add/edit images you own")),
        ("change_image", _("Edit"), _("Edit any image")),
        ("choose_image", _("Choose"), _("Select images in choosers")),
    ],
    "wagtailimages/permissions/includes/image_permissions_formset.html",
)
