from __future__ import annotations

from typing import Any, cast

from django.contrib.contenttypes.admin import GenericInlineModelAdmin
from django.contrib.contenttypes.models import ContentType
from django.http import HttpRequest
from django.utils.functional import cached_property

from polymorphic.formsets import (
    BaseGenericPolymorphicInlineFormSet,
    GenericPolymorphicFormSetChild,
    PolymorphicFormSetChild,
    polymorphic_child_forms_factory,
)

from .inlines import PolymorphicInlineModelAdmin


class GenericPolymorphicInlineModelAdmin(PolymorphicInlineModelAdmin, GenericInlineModelAdmin):
    """
    Base class for variation of inlines based on generic foreign keys.
    """

    #: The formset class
    formset: type[BaseGenericPolymorphicInlineFormSet] = BaseGenericPolymorphicInlineFormSet  # type: ignore[assignment]

    def get_formset(  # type: ignore[override]
        self, request: HttpRequest, obj: Any = None, **kwargs: Any
    ) -> type[BaseGenericPolymorphicInlineFormSet]:
        """
        Construct the generic inline formset class.
        """
        # Construct the FormSet class. This is almost the same as parent version,
        # except that a different super is called so generic_inlineformset_factory() is used.
        # NOTE that generic_inlineformset_factory() also makes sure the GFK fields are excluded in the form.
        FormSet = GenericInlineModelAdmin.get_formset(self, request, obj=obj, **kwargs)

        setattr(
            FormSet,
            "child_forms",
            polymorphic_child_forms_factory(
                formset_children=self.get_formset_children(request, obj=obj)
            ),
        )
        return cast(type[BaseGenericPolymorphicInlineFormSet], FormSet)

    class Child(PolymorphicInlineModelAdmin.Child):
        """
        Variation for generic inlines.
        """

        # Make sure that the GFK fields are excluded from the child forms
        formset_child: type[GenericPolymorphicFormSetChild] = GenericPolymorphicFormSetChild
        ct_field: str = "content_type"
        ct_fk_field: str = "object_id"

        @cached_property
        def content_type(self) -> ContentType:
            """
            Expose the ContentType that the child relates to.
            This can be used for the ``polymorphic_ctype`` field.
            """
            return ContentType.objects.get_for_model(self.model, for_concrete_model=False)

        def get_formset_child(
            self, request: HttpRequest, obj: Any = None, **kwargs: Any
        ) -> PolymorphicFormSetChild:
            # Similar to GenericInlineModelAdmin.get_formset(),
            # make sure the GFK is automatically excluded from the form
            defaults = {"ct_field": self.ct_field, "fk_field": self.ct_fk_field}
            defaults.update(kwargs)
            return super(GenericPolymorphicInlineModelAdmin.Child, self).get_formset_child(
                request, obj=obj, **defaults
            )


class GenericStackedPolymorphicInline(GenericPolymorphicInlineModelAdmin):
    """
    The stacked layout for generic inlines.
    """

    #: The default template to use.
    template: str = "admin/polymorphic/edit_inline/stacked.html"
