from django import forms
from django.contrib import admin
from django.core.exceptions import ValidationError
from django.db import models

from .models import (
    Author,
    BinaryTree,
    CapoFamiglia,
    Chapter,
    Child,
    ChildModel1,
    ChildModel2,
    Class,
    Consigliere,
    Course,
    CourseProxy,
    CourseProxy1,
    CourseProxy2,
    EditablePKBook,
    ExtraTerrestrial,
    Fashionista,
    FootNote,
    Holder,
    Holder2,
    Holder3,
    Holder4,
    Holder5,
    Inner,
    Inner2,
    Inner3,
    Inner4Stacked,
    Inner4Tabular,
    Inner5Stacked,
    Inner5Tabular,
    NonAutoPKBook,
    NonAutoPKBookChild,
    Novel,
    NovelReadonlyChapter,
    OutfitItem,
    ParentModelWithCustomPk,
    Person,
    Photo,
    Photographer,
    Poll,
    Profile,
    ProfileCollection,
    Question,
    ReadOnlyInline,
    ShoppingWeakness,
    ShowInlineChild,
    ShowInlineParent,
    Sighting,
    SomeChildModel,
    SomeParentModel,
    SottoCapo,
    Teacher,
    Title,
    TitleCollection,
    UUIDChild,
    UUIDParent,
)

site = admin.AdminSite(name="admin")


class BookInline(admin.TabularInline):
    model = Author.books.through


class NonAutoPKBookTabularInline(admin.TabularInline):
    model = NonAutoPKBook
    classes = ("collapse",)


class NonAutoPKBookChildTabularInline(admin.TabularInline):
    model = NonAutoPKBookChild
    classes = ("collapse",)


class NonAutoPKBookStackedInline(admin.StackedInline):
    model = NonAutoPKBook
    classes = ("collapse",)


class EditablePKBookTabularInline(admin.TabularInline):
    model = EditablePKBook


class EditablePKBookStackedInline(admin.StackedInline):
    model = EditablePKBook


class AuthorAdmin(admin.ModelAdmin):
    inlines = [
        BookInline,
        NonAutoPKBookTabularInline,
        NonAutoPKBookStackedInline,
        EditablePKBookTabularInline,
        EditablePKBookStackedInline,
        NonAutoPKBookChildTabularInline,
    ]


class PhotoInlineMixin:
    model = Photo
    extra = 2
    fieldsets = [
        (None, {"fields": ["image", "title"], "description": "First group"}),
        (
            "Details",
            {
                "fields": ["description", "creation_date"],
                "classes": ["collapse"],
                "description": "Second group",
            },
        ),
        (
            "Details",  # Fieldset name intentionally duplicated
            {"fields": ["update_date", "updated_by"], "description": "Third group"},
        ),
    ]


class PhotoTabularInline(PhotoInlineMixin, admin.TabularInline):
    pass


class PhotoStackedExtra2Inline(PhotoInlineMixin, admin.StackedInline):
    pass


class PhotoStackedExtra3Inline(PhotoInlineMixin, admin.StackedInline):
    extra = 3


class PhotoStackedCollapsibleInline(PhotoInlineMixin, admin.StackedInline):
    fieldsets = []
    classes = ["collapse"]


class PhotographerAdmin(admin.ModelAdmin):
    fieldsets = [
        (None, {"fields": ["firstname", "fullname"]}),
        ("Advanced options", {"fields": ["nationality", "residency"]}),
        (
            "Advanced options",  # Fieldset name intentionally duplicated
            {"fields": ["siblings", "children"], "classes": ["collapse"]},
        ),
    ]
    inlines = [
        PhotoTabularInline,
        PhotoStackedExtra2Inline,
        PhotoStackedExtra3Inline,
        PhotoStackedCollapsibleInline,
    ]


class InnerInline(admin.StackedInline):
    model = Inner
    can_delete = False
    readonly_fields = ("readonly",)  # For bug #13174 tests.


class HolderAdmin(admin.ModelAdmin):
    class Media:
        js = ("my_awesome_admin_scripts.js",)


class ReadOnlyInlineInline(admin.TabularInline):
    model = ReadOnlyInline
    readonly_fields = ["name"]


class InnerInline2(admin.StackedInline):
    model = Inner2

    class Media:
        js = ("my_awesome_inline_scripts.js",)


class InnerInline2Tabular(admin.TabularInline):
    model = Inner2


class CustomNumberWidget(forms.NumberInput):
    class Media:
        js = ("custom_number.js",)


class InnerInline3(admin.StackedInline):
    model = Inner3
    formfield_overrides = {
        models.IntegerField: {"widget": CustomNumberWidget},
    }

    class Media:
        js = ("my_awesome_inline_scripts.js",)


class TitleForm(forms.ModelForm):
    title1 = forms.CharField(max_length=100)

    def clean(self):
        cleaned_data = self.cleaned_data
        title1 = cleaned_data.get("title1")
        title2 = cleaned_data.get("title2")
        if title1 != title2:
            raise ValidationError("The two titles must be the same")
        return cleaned_data


class TitleInline(admin.TabularInline):
    model = Title
    form = TitleForm
    extra = 1


class Inner4StackedInline(admin.StackedInline):
    model = Inner4Stacked
    show_change_link = True


class Inner4TabularInline(admin.TabularInline):
    model = Inner4Tabular
    show_change_link = True


class Holder4Admin(admin.ModelAdmin):
    inlines = [Inner4StackedInline, Inner4TabularInline]


class Inner5StackedInline(admin.StackedInline):
    model = Inner5Stacked
    classes = ("collapse",)


class Inner5TabularInline(admin.TabularInline):
    model = Inner5Tabular
    classes = ("collapse",)


class Holder5Admin(admin.ModelAdmin):
    inlines = [Inner5StackedInline, Inner5TabularInline]


class InlineWeakness(admin.TabularInline):
    model = ShoppingWeakness
    extra = 1


class WeaknessForm(forms.ModelForm):
    extra_field = forms.CharField()

    class Meta:
        model = ShoppingWeakness
        fields = "__all__"


class WeaknessInlineCustomForm(admin.TabularInline):
    model = ShoppingWeakness
    form = WeaknessForm


class FootNoteForm(forms.ModelForm):
    extra_field = forms.CharField()

    class Meta:
        model = FootNote
        fields = "__all__"


class FootNoteNonEditableInlineCustomForm(admin.TabularInline):
    model = FootNote
    form = FootNoteForm

    def has_change_permission(self, request, obj=None):
        return False


class QuestionInline(admin.TabularInline):
    model = Question
    readonly_fields = ["call_me"]

    def call_me(self, obj):
        return "Callable in QuestionInline"


class PollAdmin(admin.ModelAdmin):
    inlines = [QuestionInline]

    def call_me(self, obj):
        return "Callable in PollAdmin"


class ChapterInline(admin.TabularInline):
    model = Chapter
    readonly_fields = ["call_me"]

    def call_me(self, obj):
        return "Callable in ChapterInline"


class NovelAdmin(admin.ModelAdmin):
    inlines = [ChapterInline]


class ReadOnlyChapterInline(admin.TabularInline):
    model = Chapter

    def has_change_permission(self, request, obj=None):
        return False


class NovelReadonlyChapterAdmin(admin.ModelAdmin):
    inlines = [ReadOnlyChapterInline]


class ConsigliereInline(admin.TabularInline):
    model = Consigliere


class SottoCapoInline(admin.TabularInline):
    model = SottoCapo


class ProfileInline(admin.TabularInline):
    model = Profile
    extra = 1


# admin for #18433
class ChildModel1Inline(admin.TabularInline):
    model = ChildModel1


class ChildModel2Inline(admin.StackedInline):
    model = ChildModel2


# admin for #19425 and #18388
class BinaryTreeAdmin(admin.TabularInline):
    model = BinaryTree

    def get_extra(self, request, obj=None, **kwargs):
        extra = 2
        if obj:
            return extra - obj.binarytree_set.count()
        return extra

    def get_max_num(self, request, obj=None, **kwargs):
        max_num = 3
        if obj:
            return max_num - obj.binarytree_set.count()
        return max_num


# admin for #19524
class SightingInline(admin.TabularInline):
    model = Sighting
    show_change_link = True


# admin and form for #18263
class SomeChildModelForm(forms.ModelForm):
    class Meta:
        fields = "__all__"
        model = SomeChildModel
        widgets = {
            "position": forms.HiddenInput,
        }
        labels = {"readonly_field": "Label from ModelForm.Meta"}
        help_texts = {"readonly_field": "Help text from ModelForm.Meta"}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["name"].label = "new label"


class SomeChildModelInline(admin.TabularInline):
    model = SomeChildModel
    form = SomeChildModelForm
    readonly_fields = ("readonly_field",)


class StudentInline(admin.StackedInline):
    model = Child
    extra = 1
    fieldsets = [
        ("Name", {"fields": ("name",), "classes": ("collapse",)}),
    ]


class TeacherAdmin(admin.ModelAdmin):
    inlines = [StudentInline]


class AuthorTabularInline(admin.TabularInline):
    model = Author


class FashonistaStackedInline(admin.StackedInline):
    model = Fashionista


# Admin for #30231
class ClassStackedHorizontal(admin.StackedInline):
    model = Class
    extra = 1
    filter_horizontal = ["person"]


class ClassAdminStackedHorizontal(admin.ModelAdmin):
    inlines = [ClassStackedHorizontal]


class ClassTabularHorizontal(admin.TabularInline):
    model = Class
    extra = 1
    filter_horizontal = ["person"]


class ClassAdminTabularHorizontal(admin.ModelAdmin):
    inlines = [ClassTabularHorizontal]


class ClassTabularVertical(admin.TabularInline):
    model = Class
    extra = 1
    filter_vertical = ["person"]


class ClassAdminTabularVertical(admin.ModelAdmin):
    inlines = [ClassTabularVertical]


class ClassStackedVertical(admin.StackedInline):
    model = Class
    extra = 1
    filter_vertical = ["person"]


class ClassAdminStackedVertical(admin.ModelAdmin):
    inlines = [ClassStackedVertical]


class ChildHiddenFieldForm(forms.ModelForm):
    class Meta:
        model = SomeChildModel
        fields = ["name", "position", "parent"]
        widgets = {"position": forms.HiddenInput}

    def _post_clean(self):
        super()._post_clean()
        if self.instance is not None and self.instance.position == 1:
            self.add_error(None, ValidationError("A non-field error"))


class ChildHiddenFieldTabularInline(admin.TabularInline):
    model = SomeChildModel
    form = ChildHiddenFieldForm


class ChildHiddenFieldInFieldsGroupStackedInline(admin.StackedInline):
    model = SomeChildModel
    form = ChildHiddenFieldForm
    fields = [("name", "position")]


class ChildHiddenFieldOnSingleLineStackedInline(admin.StackedInline):
    model = SomeChildModel
    form = ChildHiddenFieldForm
    fields = ("name", "position")


class ShowInlineChildInline(admin.StackedInline):
    model = ShowInlineChild


class UUIDChildInline(admin.StackedInline):
    model = UUIDChild
    exclude = ("id",)


class UUIDParentModelAdmin(admin.ModelAdmin):
    model = UUIDParent
    inlines = [UUIDChildInline]


class ShowInlineParentAdmin(admin.ModelAdmin):
    def get_inlines(self, request, obj):
        if obj is not None and obj.show_inlines:
            return [ShowInlineChildInline]
        return []


site.register(TitleCollection, inlines=[TitleInline])
# Test bug #12561 and #12778
# only ModelAdmin media
site.register(Holder, HolderAdmin, inlines=[InnerInline])
# ModelAdmin and Inline media
site.register(Holder2, HolderAdmin, inlines=[InnerInline2, InnerInline2Tabular])
# only Inline media
site.register(Holder3, inlines=[InnerInline3])

site.register(Poll, PollAdmin)
site.register(Novel, NovelAdmin)
site.register(NovelReadonlyChapter, NovelReadonlyChapterAdmin)
site.register(Fashionista, inlines=[InlineWeakness])
site.register(Holder4, Holder4Admin)
site.register(Holder5, Holder5Admin)
site.register(Author, AuthorAdmin)
site.register(
    CapoFamiglia, inlines=[ConsigliereInline, SottoCapoInline, ReadOnlyInlineInline]
)
site.register(ProfileCollection, inlines=[ProfileInline])
site.register(ParentModelWithCustomPk, inlines=[ChildModel1Inline, ChildModel2Inline])
site.register(BinaryTree, inlines=[BinaryTreeAdmin])
site.register(ExtraTerrestrial, inlines=[SightingInline])
site.register(SomeParentModel, inlines=[SomeChildModelInline])
site.register([Question, Inner4Stacked, Inner4Tabular, Sighting])
site.register(Teacher, TeacherAdmin)
site.register(Chapter, inlines=[FootNoteNonEditableInlineCustomForm])
site.register(OutfitItem, inlines=[WeaknessInlineCustomForm])
site.register(Person, inlines=[AuthorTabularInline, FashonistaStackedInline])
site.register(Photographer, PhotographerAdmin)
site.register(Course, ClassAdminStackedHorizontal)
site.register(CourseProxy, ClassAdminStackedVertical)
site.register(CourseProxy1, ClassAdminTabularVertical)
site.register(CourseProxy2, ClassAdminTabularHorizontal)
site.register(ShowInlineParent, ShowInlineParentAdmin)
site.register(UUIDParent, UUIDParentModelAdmin)
# Used to test hidden fields in tabular and stacked inlines.
site2 = admin.AdminSite(name="tabular_inline_hidden_field_admin")
site2.register(SomeParentModel, inlines=[ChildHiddenFieldTabularInline])
site3 = admin.AdminSite(name="stacked_inline_hidden_field_in_group_admin")
site3.register(SomeParentModel, inlines=[ChildHiddenFieldInFieldsGroupStackedInline])
site4 = admin.AdminSite(name="stacked_inline_hidden_field_on_single_line_admin")
site4.register(SomeParentModel, inlines=[ChildHiddenFieldOnSingleLineStackedInline])
