from django.contrib.contenttypes.forms import BaseGenericInlineFormSet, generic_inlineformset_factory
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.forms.models import ModelForm

from .models import BasePolymorphicModelFormSet, polymorphic_child_forms_factory, PolymorphicFormSetChild


class GenericPolymorphicFormSetChild(PolymorphicFormSetChild):
    """
    Formset child for generic inlines
    """

    def __init__(self, *args, **kwargs):
        self.ct_field = kwargs.pop('ct_field', 'content_type')
        self.fk_field = kwargs.pop('fk_field', 'object_id')
        super(GenericPolymorphicFormSetChild, self).__init__(*args, **kwargs)

    def get_form(self, ct_field="content_type", fk_field="object_id", **kwargs):
        """
        Construct the form class for the formset child.
        """
        exclude = list(self.exclude)
        extra_exclude = kwargs.pop('extra_exclude', None)
        if extra_exclude:
            exclude += list(extra_exclude)

        # Make sure the GFK fields are excluded by default
        # This is similar to what generic_inlineformset_factory() does
        # if there is no field called `ct_field` let the exception propagate
        opts = self.model._meta
        ct_field = opts.get_field(self.ct_field)

        if not isinstance(ct_field, models.ForeignKey) or ct_field.remote_field.model != ContentType:
            raise Exception("fk_name '%s' is not a ForeignKey to ContentType" % ct_field)

        fk_field = opts.get_field(self.fk_field)  # let the exception propagate
        exclude.extend([ct_field.name, fk_field.name])
        kwargs['exclude'] = exclude

        return super(GenericPolymorphicFormSetChild, self).get_form(**kwargs)


class BaseGenericPolymorphicInlineFormSet(BaseGenericInlineFormSet, BasePolymorphicModelFormSet):
    """
    Polymorphic formset variation for inline generic formsets
    """


def generic_polymorphic_inlineformset_factory(model, formset_children, form=ModelForm,
                                              formset=BaseGenericPolymorphicInlineFormSet,
                                              ct_field="content_type", fk_field="object_id",
                                              # Base form
                                              # TODO: should these fields be removed in favor of creating
                                              # the base form as a formset child too?
                                              fields=None, exclude=None,
                                              extra=1, can_order=False, can_delete=True,
                                              max_num=None, formfield_callback=None,
                                              validate_max=False, for_concrete_model=True,
                                              min_num=None, validate_min=False, child_form_kwargs=None):
    """
    Construct the class for a generic inline polymorphic formset.

    All arguments are identical to :func:`~django.contrib.contenttypes.forms.generic_inlineformset_factory`,
    with the exception of the ``formset_children`` argument.

    :param formset_children: A list of all child :class:`PolymorphicFormSetChild` objects
                             that tell the inline how to render the child model types.
    :type formset_children: Iterable[PolymorphicFormSetChild]
    :rtype: type
    """
    kwargs = {
        'model': model,
        'form': form,
        'formfield_callback': formfield_callback,
        'formset': formset,
        'ct_field': ct_field,
        'fk_field': fk_field,
        'extra': extra,
        'can_delete': can_delete,
        'can_order': can_order,
        'fields': fields,
        'exclude': exclude,
        'min_num': min_num,
        'max_num': max_num,
        'validate_min': validate_min,
        'validate_max': validate_max,
        'for_concrete_model': for_concrete_model,
        # 'localized_fields': localized_fields,
        # 'labels': labels,
        # 'help_texts': help_texts,
        # 'error_messages': error_messages,
        # 'field_classes': field_classes,
    }
    if child_form_kwargs is None:
        child_form_kwargs = {}

    child_kwargs = {
        # 'exclude': exclude,
        'ct_field': ct_field,
        'fk_field': fk_field,
    }
    if child_form_kwargs:
        child_kwargs.update(child_form_kwargs)

    FormSet = generic_inlineformset_factory(**kwargs)
    FormSet.child_forms = polymorphic_child_forms_factory(formset_children, **child_kwargs)
    return FormSet
