import copy
import typing

from django.contrib import admin
from django.contrib.auth.models import Permission
from django.utils.html import format_html
from fieldsets_with_inlines import FieldsetsInlineMixin
from import_export import resources
from nested_admin import (NestedModelAdmin, NestedStackedInline,
                          NestedTabularInline)
from rangefilter.filters import DateRangeFilter

from shared.admin import MarkdownxGuardedModelAdmin

from .forms import ContractAdminForm, ContractFileAdminForm, SigneeAdminForm
from .models import (Contract, Contractee, ContracteeSignature,
                     ContracteeSignatureRepresentative, ContractFile,
                     ContractFilingArea, ContractIntent, ContractIssue,
                     ContractType, Signee, SigneeSignature,
                     SigneeSignatureRepresentative)


class ContractResource(resources.ModelResource):
    class Meta:
        model = Contract


class IndexHiddenModelAdmin(MarkdownxGuardedModelAdmin):
    def has_module_permission(self, request):
        return False


def permissions_mixin_factory(
    change_permission: str,
    delete_permission: str,
    obj_conditional: typing.Callable,
    change_attr_func: typing.Callable = lambda obj: obj,
    delete_attr_func: typing.Callable = lambda obj: obj,
) -> object:
    class Mixin:
        def has_change_permission(self, request, obj=None) -> bool:
            if (
                obj is not None
                and obj_conditional(request, obj)
                and not request.user.has_perm(change_permission, change_attr_func(obj))
            ):
                return False

            return super().has_change_permission(request, obj)

        def has_delete_permission(self, request, obj=None) -> bool:
            if (
                obj is not None
                and obj_conditional(request, obj)
                and not request.user.has_perm(delete_permission, delete_attr_func(obj))
            ):
                return False

            return super().has_change_permission(request, obj)

    return Mixin


get_obj_contract = lambda obj: obj.contract


OwnPermissionsMixin = permissions_mixin_factory(
    "contracts.edit_others",
    "contracts.delete_others",
    lambda request, obj: obj.created_by != request.user,
)


def own_permissions_mixin_save_model(self, request, obj, form, change):
    if obj.created_by is None:
        obj.created_by = request.user

    return super().save_model(request, obj, form, change)


OwnPermissionsMixin.save_model = own_permissions_mixin_save_model


ParentContractApprovedPermissionsMixin = permissions_mixin_factory(
    "contracts.edit_when_approved",
    "contracts.delete_when_approved",
    lambda request, obj: get_obj_contract(obj).is_approved,
    get_obj_contract,
    get_obj_contract,
)


ParentContractOwnPermissionsMixin = permissions_mixin_factory(
    "contracts.edit_others",
    "contracts.delete_others",
    lambda request, obj: get_obj_contract(obj).created_by != request.user,
    get_obj_contract,
    get_obj_contract,
)


# BEGIN Contracts


class ContractFileAdmin(
    IndexHiddenModelAdmin,
    ParentContractApprovedPermissionsMixin,
    ParentContractOwnPermissionsMixin,
):
    form = ContractFileAdminForm


class ContracteeSignatureRepresentativeInline(NestedStackedInline):
    model = ContracteeSignatureRepresentative
    extra = 0


class ContracteeSignatureInline(NestedStackedInline):
    model = ContracteeSignature
    autocomplete_fields = ("contractee",)
    inlines = (ContracteeSignatureRepresentativeInline,)
    extra = 0


class SigneeSignatureRepresentativeInline(NestedStackedInline):
    model = SigneeSignatureRepresentative
    extra = 0


class SigneeSignatureInline(NestedStackedInline):
    model = SigneeSignature
    autocomplete_fields = ("signee",)
    inlines = (SigneeSignatureRepresentativeInline,)
    extra = 0


class ContractFileInline(NestedTabularInline):
    model = ContractFile
    form = ContractFileAdminForm
    extra = 0


class ContractIntentInline(NestedTabularInline):
    model = ContractIntent
    extra = 0


class ContractAdmin(
    OwnPermissionsMixin,
    permissions_mixin_factory(
        "contracts.edit_when_approved",
        "contracts.delete_when_approved",
        lambda request, obj: obj.is_approved,
    ),
    MarkdownxGuardedModelAdmin,
    FieldsetsInlineMixin,
    NestedModelAdmin,
):
    form = ContractAdminForm

    readonly_fields = ("created_by",)
    autocomplete_fields = (
        "types",
        "filing_area",
        "issues",
    )

    fieldsets_with_inlines = [
        (
            "Základní informace",
            {
                "fields": [
                    "name",
                    "id_number",
                    "types",
                    "summary",
                    "is_public",
                    "legal_state",
                    "primary_contract",
                ]
            },
        ),
        (
            "Data",
            {
                "fields": [
                    "valid_start_date",
                    "valid_end_date",
                ]
            },
        ),
        (
            "Náklady",
            {
                "fields": [
                    "cost_amount",
                    "cost_unit",
                ]
            },
        ),
        ContractFileInline,
        ContracteeSignatureInline,
        SigneeSignatureInline,
        (
            "Odkazy",
            {
                "fields": [
                    "tender_url",
                    "agreement_url",
                ]
            },
        ),
        ContractIntentInline,
        (
            "Fyzický dokument",
            {
                "fields": [
                    "paper_form_state",
                    "filing_area",
                ]
            },
        ),
        (
            "Doplňující informace",
            {
                "fields": [
                    "issues",
                    "notes",
                ]
            },
        ),
    ]

    def get_form(self, request, *args, **kwargs) -> ContractAdminForm:
        form = super().get_form(request, *args, **kwargs)
        form.current_user = request.user

        return form

    def get_fieldsets(self, request, obj=None):
        fieldsets_with_inlines = copy.deepcopy(self.fieldsets_with_inlines)

        if (
            obj is None  # Creating confidential data, creator will be request.user
            or obj.created_by == request.user
            or request.user.has_perm("view_confidential", obj)
        ):
            fieldsets_with_inlines[0][1]["fields"].insert(
                fieldsets_with_inlines[0][1]["fields"].index("is_public") + 1,
                "publishing_rejection_comment",
            )

        if request.user.is_superuser or request.user.has_perm("approve", self):
            fieldsets_with_inlines.insert(
                8,
                ("Schválení", {"fields": ["is_approved"]}),
            )

        return [
            self.make_placeholder(index, fieldset)
            for index, fieldset in enumerate(fieldsets_with_inlines)
        ]

    def get_queryset(self, request):
        queryset = super().get_queryset(request)

        if not request.user.has_perm("contracts.view_confidential"):
            queryset = queryset.filter(is_public=True)

        if not request.user.has_perm("contracts.approve"):
            queryset = queryset.filter(is_approved=True)

        return queryset

    def save_model(self, request, obj, form, change):
        if obj.valid_start_date is None:
            last_signature_date = None

            for signature_set in (
                obj.contractee_signatures.all(),
                obj.signee_signatures.all(),
            ):
                for signature in signature_set:
                    if (
                        last_signature_date is None
                        or last_signature_date < signature.date
                    ):
                        last_signature_date = signature.date

            obj.valid_start_date = last_signature_date

        return super().save_model(request, obj, form, change)

    def has_change_permission(self, request, obj=None):
        if (
            obj is not None
            and obj.is_approved
            and not request.user.has_perm("contracts.edit_when_approved", obj)
        ):
            return False

        return super().has_change_permission(request, obj)

    def has_delete_permission(self, request, obj=None):
        if (
            obj is not None
            and obj.is_approved
            and not request.user.has_perm("contracts.delete_when_approved", obj)
        ):
            return False

        return super().has_change_permission(request, obj)

    list_filter = (
        "types",
        "is_approved",
        "legal_state",
        "is_public",
        "paper_form_state",
        "issues",
        ("all_parties_sign_date", DateRangeFilter),
        ("valid_start_date", DateRangeFilter),
        ("valid_end_date", DateRangeFilter),
    )

    list_display = (
        "name",
        "is_approved",
        "is_public",
    )


class ContractTypeAdmin(MarkdownxGuardedModelAdmin):
    model = ContractType
    ordering = ("name",)
    search_fields = ("name",)


class ContractIssueAdmin(MarkdownxGuardedModelAdmin):
    model = ContractIssue
    ordering = ("name",)
    search_fields = ("name",)


class ContractFilingAreaAdmin(MarkdownxGuardedModelAdmin):
    model = ContractFilingArea
    ordering = ("name",)
    search_fields = (
        "name",
        "person_responsible",
    )


# END Contracts

# BEGIN Signing parties


class ContracteeAdmin(OwnPermissionsMixin, MarkdownxGuardedModelAdmin):
    model = Contractee

    search_fields = (
        "name",
        "department",
        "role",
    )
    ordering = ("name",)


class SigneeAdmin(OwnPermissionsMixin, MarkdownxGuardedModelAdmin):
    model = Signee

    search_fields = (
        "name",
        "department",
        "role",
    )
    ordering = ("name",)

    form = SigneeAdminForm

    readonly_fields = ("load_ares_data_button",)

    list_filter = ("entity_type",)
    list_display = ("name", "entity_type")

    def get_fields(self, request, obj=None):
        fields = [
            "name",
            "entity_type",
            "ico_number",
            "department",
            "role",
        ]

        if (
            obj is None  # Creating
            or obj.entity_has_public_address
            or request.user.has_perm("contracts.view_confidential", obj)
        ):
            entity_type_index = fields.index("entity_type") + 1

            fields[entity_type_index:entity_type_index] = [
                "address_street_with_number",
                "address_district",
                "address_zip",
                "address_country",
            ]

            fields.insert(
                fields.index("department"),
                "date_of_birth",
            )

        if obj is None or request.user.has_perm(  # Allowed to create
            "contracts.edit_signee", obj
        ):
            fields.insert(fields.index("ico_number"), "load_ares_data_button")

        return fields

    def load_ares_data_button(self, obj):
        return format_html(
            '<button type="button" id="load_ares_data">Načíst data</button>'
        )

    load_ares_data_button.allow_tags = True
    load_ares_data_button.short_description = "ARES"


get_obj_signee_contract = lambda obj: obj.signee.contract
get_obj_contractee_contract = lambda obj: obj.contractee.contract


class SigneeSignatureRepresentativeAdmin(
    IndexHiddenModelAdmin,
    permissions_mixin_factory(
        "contracts.edit_when_approved",
        "contracts.delete_when_approved",
        lambda request, obj: get_obj_signee_contract(obj).is_approved,
        get_obj_signee_contract,
        get_obj_signee_contract,
    ),
     permissions_mixin_factory(
        "contracts.edit_others",
        "contracts.delete_others",
        lambda request, obj: get_obj_contractee_contract(obj).created_by != request.user,
        get_obj_signee_contract,
        get_obj_signee_contract,
    ),
):
    pass


class ContracteeSignatureRepresentativeAdmin(
    IndexHiddenModelAdmin,
    permissions_mixin_factory(
        "contracts.edit_when_approved",
        "contracts.delete_when_approved",
        lambda request, obj: get_obj_contractee_contract(obj).is_approved,
        get_obj_contractee_contract,
        get_obj_contractee_contract,
    ),
     permissions_mixin_factory(
        "contracts.edit_others",
        "contracts.delete_others",
        lambda request, obj: get_obj_contractee_contract(obj).created_by != request.user,
        get_obj_contractee_contract,
        get_obj_contractee_contract,
    ),
):
    pass


# END Signing parties


class ContractSubmodelAdmin(
    IndexHiddenModelAdmin,
    ParentContractApprovedPermissionsMixin,
    ParentContractOwnPermissionsMixin,
):
    pass


admin.site.register(Permission)

for model in (
    SigneeSignature,
    ContracteeSignature,
    ContractIntent,
):
    admin.site.register(model, ContractSubmodelAdmin)

admin.site.register(SigneeSignatureRepresentative, SigneeSignatureRepresentativeAdmin)
admin.site.register(ContracteeSignatureRepresentative, ContracteeSignatureRepresentativeAdmin)

admin.site.register(ContractType, ContractTypeAdmin)
admin.site.register(ContractIssue, ContractIssueAdmin)
admin.site.register(ContractFile, ContractFileAdmin)
admin.site.register(ContractFilingArea, ContractFilingAreaAdmin)

admin.site.register(Contractee, ContracteeAdmin)
admin.site.register(Signee, SigneeAdmin)

admin.site.register(Contract, ContractAdmin)