from django import forms
from django.forms.models import ModelFormMetaclass
from django.utils.translation import gettext
from odin import registration
from odin.utils import getmeta

from .models import FieldMappingModel, MappingModel
from .registry import mapping_action_registry

NOTHING = [(None, "---")]


def clean_model_formfactory(
    model,
    clean_method,
    form=forms.ModelForm,
    fields=None,
    exclude=None,
    formfield_callback=None,
    widgets=None,
    localized_fields=None,
    labels=None,
    help_texts=None,
    error_messages=None,
    field_classes=None,
):
    """
    clean_model_formfactory does the same as forms.modelform_factory, but
    allows you to specify a forms.clean method
    """
    model_form = forms.modelform_factory(
        model,
        form=form,
        fields=fields,
        exclude=exclude,
        formfield_callback=formfield_callback,
        widgets=widgets,
        localized_fields=localized_fields,
        labels=labels,
        help_texts=help_texts,
        error_messages=error_messages,
        field_classes=field_classes,
    )

    return ModelFormMetaclass(
        "%sCleanForm" % model.__name__, (model_form,), {"clean": clean_method}
    )


def action_choices(mapping_class):
    mapping_actions = getattr(mapping_class, "action_choices", [])
    return mapping_action_registry.get(mapping_class) + mapping_actions


def display_name(value):
    translated_value = gettext(value)
    if translated_value != value:
        return "%s (%s)" % (translated_value.capitalize(), value)

    return value


def mappingmodelformset_factory(mapping_class, **kwargs):
    excluded_fields = set(getattr(mapping_class, "excluded_fields", []))
    fields = set(getmeta(mapping_class.from_obj).all_fields) - excluded_fields

    from_mapping_field_choices = sorted(
        [(f.name, display_name(f.name)) for f in fields]
        +
        # nested fields, eg; shipping_address.line4
        [
            (attrname, attrname)
            for attrname in registration.get_field_resolver(
                mapping_class.from_obj
            ).from_field_dict.keys()
            if "." in attrname
        ]
    )
    to_mapping_field_choices = sorted(
        [(f.name, display_name(f.name)) for f in getmeta(mapping_class.to_obj).fields]
    )

    if hasattr(mapping_class, "get_extra_to_obj_field_choices"):
        to_mapping_field_choices += mapping_class.get_extra_to_obj_field_choices()

    # the formfield_callback is called to create the form_fields for the
    # formset created by inlineformset_factory. By default it just returns
    # field.form_fields(**field_kwargs) but it can be used to add some special
    # processing for certain fields.
    def create_field(field, **field_kwargs):
        if field.name == "from_fields":
            return forms.MultipleChoiceField(
                choices=NOTHING + from_mapping_field_choices,
                widget=forms.SelectMultiple(
                    attrs={"class": "select2", "multiple": "multiple"}
                ),
                **field_kwargs,
            )
        elif field.name == "to_fields":
            return forms.MultipleChoiceField(
                choices=NOTHING + to_mapping_field_choices,
                widget=forms.SelectMultiple(
                    attrs={"class": "select2", "multiple": "multiple"}
                ),
                **field_kwargs,
            )
        elif field.name == "action_importstring":
            return forms.ChoiceField(
                choices=action_choices(mapping_class), required=False, **field_kwargs
            )

        return field.formfield(**field_kwargs)

    kwargs.setdefault("fields", ["from_fields", "action_importstring", "to_fields"])

    # it could be that the mapping_class has a clean_form method defines to clean
    # the FieldMappingModel form. If so, we will create the form ourself instead of
    # using forms.ModelForm
    form = forms.ModelForm
    if hasattr(mapping_class, "clean_form"):

        def clean_form(self):
            return mapping_class.clean_form(self)

        form = clean_model_formfactory(FieldMappingModel, clean_form, **kwargs)
    kwargs.setdefault("form", form)

    kwargs.setdefault("can_delete", True)
    kwargs.setdefault("extra", 3)
    kwargs.setdefault("formfield_callback", create_field)
    return forms.inlineformset_factory(MappingModel, FieldMappingModel, **kwargs)
