import logging
from typing import Any

from django.conf import settings
from django.contrib import admin
from django.contrib.admin import FieldListFilter
from django.contrib.auth.admin import UserAdmin
from django.contrib.contenttypes.admin import GenericTabularInline
from django.contrib.gis.admin import GISModelAdmin
from django.db.models import F, QuerySet
from django.http import HttpRequest
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _

from .models import (
    ActivityLogEntry,
    Assembly,
    AssemblyLink,
    AssemblyMember,
    Badge,
    BadgeCategory,
    BadgeToken,
    BadgeTokenTimeConstraint,
    BulletinBoardEntry,
    Conference,
    ConferenceMember,
    ConferenceNavigationItem,
    ConferenceTag,
    ConferenceTrack,
    DereferrerStats,
    Event,
    EventAttachment,
    EventParticipant,
    Invitation,
    Link,
    Lock,
    MapFloor,
    MapPOI,
    MetaNavItem,
    PlatformUser,
    Project,
    Room,
    RoomLink,
    RoomShare,
    ScheduleSource,
    ScheduleSourceImport,
    ScheduleSourceMapping,
    StaticPage,
    StaticPageNamespace,
    StaticPageRevision,
    TagItem,
    UserBadge,
    UserCommunicationChannel,
    UserContact,
    UserDereferrerAllowlist,
    Voucher,
    VoucherEntry,
)

logger = logging.getLogger(__name__)


class ArrayFieldEntryFilter(FieldListFilter):
    """
    A ModelAdmin list_filter which can be assigned to an ArrayField,
    i.e. `list_filter = ['conference', ('groups', ArrayFieldEntryFilter)]`
    """

    def __init__(self, field, request, params, model, model_admin, field_path):
        self.model_admin = model_admin
        self.parameter_name = field_path
        super().__init__(field, request, params, model, model_admin, field_path)

    def expected_parameters(self):
        return [self.parameter_name]

    def value(self):
        """Fetch currently selected value (from query string)."""
        return self.used_parameters.get(self.parameter_name)

    def choices(self, changelist):
        values = set()

        # fetch all possible groups by merging all entries in all objects' field value
        for array_value in self.model_admin.model.objects.values_list(self.field_path, flat=True):
            values.update(array_value or [])

        # assemble available options
        v = self.value()
        yield {
            'selected': v is None or v == '',
            'query_string': changelist.get_query_string(remove=[self.parameter_name]),
            'display': _('All'),
        }
        for value in sorted(values, key=lambda x: x.upper()):
            yield {
                'selected': v == value,
                'query_string': changelist.get_query_string({self.parameter_name: value}),
                'display': value,
            }

    def queryset(self, request, queryset):
        if query := self.value():
            qs_filter = {
                f'{self.field_path}__contains': query.split(','),
            }
            queryset = queryset.filter(**qs_filter)
        return queryset


class UserCommunicationInline(admin.TabularInline):
    model = UserCommunicationChannel
    extra = 0


class UserConferenceInline(admin.TabularInline):
    model = ConferenceMember
    extra = 0


class UserContactInline(admin.TabularInline):
    model = UserContact
    fk_name = 'user'
    extra = 0


class UserBadgeInline(admin.TabularInline):
    model = UserBadge
    fk_name = 'user'
    extra = 0


class UserAssemblyMemberInline(admin.TabularInline):
    model = AssemblyMember
    extra = 0
    fields = ['assembly', 'can_manage_assembly', 'is_representative', 'show_public']


class UserFavoriteEventInline(admin.TabularInline):
    model = Event.favorite_of.through
    extra = 0
    verbose_name = _('PlatformUser__favorite_event')
    verbose_name_plural = _('PlatformUser__favorite_events')


class UserFavoriteAssemblyInline(admin.TabularInline):
    model = Assembly.favorite_of.through
    extra = 0
    verbose_name = _('PlatformUser__favorite_assembly')
    verbose_name_plural = _('PlatformUser__favorite_assemblies')


class PlatformUserAdmin(UserAdmin):
    model = PlatformUser

    list_display = ['username', 'user_type']
    list_filter = ['user_type', 'is_active', 'is_staff']
    search_fields = ['username', 'display_name', 'communication_channels__address']

    fieldsets = (
        (None, {'fields': ('username', 'slug', 'password', 'user_type', 'timezone')}),
        ('Personal info', {'fields': ('first_name', 'last_name', 'display_name', 'email')}),
        ('Self Portrayal', {'fields': (('pronouns', 'show_name'), ('status', 'status_public'), ('avatar', 'avatar_url'))}),
        ('Accessibility', {'fields': ('theme', 'no_animations', 'colorblind', 'high_contrast', 'tag_ignorelist')}),
        ('Disturbance Settings', {'fields': ('receive_dms', 'receive_dm_images', 'receive_audio', 'receive_video', 'autoaccept_contacts')}),
        ('Permissions', {'fields': ('is_active', 'shadow_banned', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}),
        ('Notifications', {'fields': ('admin_notification',)}),
        ('Important dates', {'fields': ('last_login', 'date_joined')}),
        ('Security', {'fields': ('allow_reset_non_primary',)}),
    )

    inlines = [
        UserCommunicationInline,
        UserConferenceInline,
        UserContactInline,
        UserBadgeInline,
        UserAssemblyMemberInline,
        UserFavoriteEventInline,
        UserFavoriteAssemblyInline,
    ]

    readonly_fields = ['id']

    def get_inline_instances(self, request, obj=None):
        # do not show any inlines when creating a new user
        if not obj:
            return []

        return super().get_inline_instances(request, obj)


class ConferenceTrackInline(admin.TabularInline):
    model = ConferenceTrack
    fields = ['slug', 'name']
    extra = 0


class ConferenceAdmin(admin.ModelAdmin):
    list_display = ['slug', 'name']
    list_display_links = ['slug', 'name']
    search_fields = ['slug', 'name']
    inlines = [ConferenceTrackInline]
    readonly_fields = ['id']

    def get_inline_instances(self, request, obj=None, **kwargs):
        # no inlines on new instances
        if obj is None:
            return []
        return super().get_inline_instances(request, obj, **kwargs)


class ConferenceMemberAdmin(admin.ModelAdmin):
    list_display = ['conference', 'user', 'is_staff', 'active_angel']
    list_display_links = ['user']
    list_filter = ['conference__slug', 'is_staff', 'active_angel']
    search_fields = ['user__username', 'roles']


class ConferenceNavigationItemInline(admin.TabularInline):
    model = ConferenceNavigationItem
    fields = ['index', 'is_visible', 'icon', 'label', 'title', 'url']

    def has_add_permission(self, request, obj):
        return False

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

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


class ConferenceNavigationItemAdmin(admin.ModelAdmin):
    list_display = ['conference', 'parent', 'index', 'is_visible', 'label']
    list_display_links = ['label']
    list_filter = ['conference', 'is_visible']
    search_fields = ['label', 'title']

    def get_queryset(self, request, *args, **kwargs):
        qs = super().get_queryset(request, *args, **kwargs)
        return qs.order_by('conference__name', F('parent__index').asc(nulls_first=True), 'index')

    fieldsets = (
        (
            'Organisation',
            {
                'fields': ['conference', 'parent', 'index'],
            },
        ),
        (
            'Data',
            {
                'fields': [
                    'is_visible',
                    'icon',
                    ('label_de', 'label_en'),
                    ('title_de', 'title_en'),
                    'url',
                ],
            },
        ),
    )

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == 'parent':
            kwargs['queryset'] = ConferenceNavigationItem.objects.filter(parent=None).order_by('index')

        return super().formfield_for_foreignkey(db_field, request, **kwargs)

    def get_inlines(self, request, obj):
        return [ConferenceNavigationItemInline] if obj is not None and obj.parent is None else []

    def get_readonly_fields(self, request, obj=None, **kwargs):
        # once set, the conference may not be changed any more
        return [] if obj is None else ['conference']


class ConferenceTrackAdmin(admin.ModelAdmin):
    list_display = ['conference', 'is_public', 'slug', 'name']
    list_display_links = ['slug', 'name']
    list_filter = ['conference', 'is_public']
    search_fields = ['slug', 'name']
    readonly_fields = ['conference']

    fieldsets = (
        (
            'Organisation',
            {
                'fields': ['conference', 'is_public'],
            },
        ),
        (
            'Data',
            {
                'fields': ['slug', 'name', 'color', 'banner_image'],
            },
        ),
    )

    def get_readonly_fields(self, request, obj=None, **kwargs):
        # conference may be selected on new tracks
        if obj is None:
            return []
        return super().get_readonly_fields(request, obj, **kwargs)


class ConferenceTagAdmin(admin.ModelAdmin):
    list_display = ['conference', 'slug', 'is_public', 'value_type']
    list_display_links = ['slug']
    list_filter = ['value_type', 'is_public', 'conference']
    search_fields = ['slug', 'description']


class TagsInline(GenericTabularInline):
    model = TagItem
    ct_field = 'target_type'
    ct_fk_field = 'target_id'
    extra = 1


class BadgeInline(admin.TabularInline):
    model = Badge
    list_display = ['name', 'show_public', 'is_achievement']
    extra = 1


class AssemblyLinkInline(admin.TabularInline):
    model = AssemblyLink
    fk_name = 'a'

    list_display = ['type', 'b', 'is_public']
    extra = 0


class AssemblyMemberInline(admin.TabularInline):
    model = AssemblyMember
    extra = 0
    fields = ['member', 'can_manage_assembly', 'is_representative', 'show_public']


class ActivityLogEntryInline(GenericTabularInline):
    model = ActivityLogEntry
    ct_field = 'entity_content_type'
    ct_fk_field = 'entity_object_id'
    extra = 0
    fields = ['timestamp', 'kind', 'user', 'comment', 'changes']
    readonly_fields = ['timestamp']

    def has_add_permission(self, request, obj):
        return False

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

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


class AssemblyAdmin(GISModelAdmin):
    list_display = ['conference', 'parent', 'slug', 'name', 'state', 'is_official']
    list_display_links = ['slug', 'name']
    list_filter = ['conference', 'parent', 'state', 'is_official']
    readonly_fields = ['id', 'conference']
    search_fields = ['slug', 'name', 'description']
    inlines = [TagsInline, BadgeInline, AssemblyLinkInline, AssemblyMemberInline, ActivityLogEntryInline]

    fieldsets = (
        (
            'Organisation',
            {
                'fields': ['id', 'conference', 'state', 'hierarchy', 'parent', 'is_official'],
            },
        ),
        (
            'Data',
            {
                'fields': ['is_physical', 'is_virtual', 'is_remote', 'slug', 'name', 'description', 'assembly_link', 'banner_image'],
            },
        ),
        (
            'Registration',
            {
                'fields': ['registration_details'],
            },
        ),
        (
            'Location',
            {
                'fields': ['assembly_location', 'location_point', 'location_boundaries'],
            },
        ),
    )

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        assembly_id = request.resolver_match.kwargs.get('object_id')
        if assembly_id is not None:
            assembly = Assembly.objects.get(id=assembly_id)

            # parent field may only list assemblies from the same conference
            if db_field.name == 'parent':
                kwargs['queryset'] = Assembly.objects.filter(conference=assembly.conference)

        return super().formfield_for_foreignkey(db_field, request, **kwargs)

    def get_fieldsets(self, request, obj=None, **kwargs):
        if obj is None:
            return [
                (
                    'Organisation',
                    {
                        'fields': ['id', 'conference', 'state', 'hierarchy', 'is_official'],
                    },
                ),
                (
                    'Data',
                    {
                        'fields': ['is_physical', 'is_virtual', 'is_remote', 'slug', 'name'],
                    },
                ),
            ]
        return super().get_fieldsets(request, obj, **kwargs)

    def get_readonly_fields(self, request, obj=None, **kwargs):
        if obj is None:
            return ['id']
        return super().get_readonly_fields(request, obj, **kwargs)

    def get_inline_instances(self, request, obj=None, **kwargs):
        if obj is None:
            return []
        return super().get_inline_instances(request, obj, **kwargs)


class ActivityLogEntryAdmin(admin.ModelAdmin):
    list_display = ['timestamp', 'entity', 'kind', 'user', 'comment', 'changes']
    list_filter = ['kind', 'entity_content_type', 'user']
    search_fields = ['entity__slug', 'entity__name', 'comment', 'changes']
    readonly_fields = ['timestamp', 'conference', 'context', 'entity']
    fieldsets = [
        (
            'Organisation',
            {
                'fields': ['conference', 'timestamp', 'context', 'entity'],
            },
        ),
        (
            'Data',
            {
                'fields': [
                    (
                        'kind',
                        'user',
                    ),
                    'comment',
                    'changes',
                ],
            },
        ),
    ]


class InvitationAdmin(admin.ModelAdmin):
    model = Invitation
    readonly_fields = ['id', 'type', 'sender', 'requested_at', 'updated_at', 'requester', 'requested']

    def get_fields(self, request: HttpRequest, obj: Any | None = None) -> list[str]:
        if obj is None:
            return ['requester_type', 'requester_id', 'requested_type', 'requested_id', 'comment']
        return ['id', 'requester', 'requester_type', 'requested', 'requested_type', 'comment', 'requested_at', 'updated_at', 'type', 'sender']

    def get_readonly_fields(self, request: HttpRequest, obj: Any | None = None) -> list[str]:
        if obj is None:
            return self.readonly_fields
        return [*self.readonly_fields, 'requester_type', 'requested_type']


class MapFloorAdmin(admin.ModelAdmin):
    list_display = ['index', 'name', 'conference']
    list_display_links = ['name']
    list_filter = ['conference']
    readonly_fields = ['id', 'conference']
    ordering = ['conference', 'index']

    fieldsets = (
        (
            'Organisation',
            {
                'fields': ['id', 'conference'],
            },
        ),
        (
            'Data',
            {
                'fields': ['index', ('name_de', 'name_en')],
            },
        ),
    )

    def get_readonly_fields(self, request, obj=None, **kwargs):
        result = list(super().get_readonly_fields(request, obj, **kwargs))
        if obj is None:
            result.remove('conference')
        return result


class MapPOIAdmin(GISModelAdmin):
    list_display = ['visible', 'name', 'is_official', 'conference']
    list_display_links = ['name']
    list_filter = ['conference', 'visible', 'is_official']
    readonly_fields = ['id', 'conference']
    search_fields = ['name', 'description']

    fieldsets = (
        (
            'Organisation',
            {
                'fields': ['id', 'conference'],
            },
        ),
        (
            'Data',
            {
                'fields': ['visible', 'is_official', ('name_de', 'name_en'), ('description_de', 'description_en')],
            },
        ),
        (
            'Location',
            {
                'fields': ['location_floor', 'location_point'],
            },
        ),
    )

    def get_readonly_fields(self, request, obj=None, **kwargs):
        result = list(super().get_readonly_fields(request, obj, **kwargs))
        if obj is None:
            result.remove('conference')
        return result


class BadgeCategoryAdmin(admin.ModelAdmin):
    model = BadgeCategory


class BadgeAdmin(admin.ModelAdmin):
    model = Badge

    fields = ['name', 'issuing_assembly', 'state', 'category', 'symbol', 'description', 'location', 'image', 'issuing_token']
    readonly_fields = [
        'issuing_assembly',
    ]
    list_display = ['__str__', 'issuing_assembly', 'category']
    list_display_links = ['__str__']
    list_filter = ['state', 'category']
    search_fields = ['name', 'description', 'issuing_token']


class BadgeTokenTimeConstraintInline(admin.TabularInline):
    model = BadgeTokenTimeConstraint
    fields = ['date_time_range']
    verbose_name = _('BadgeToken__badge_token_time_constraint')
    verbose_name_plural = _('BadgeToken__badge_token_time_constraints')


class BadgeTokenAdmin(admin.ModelAdmin):
    model = BadgeToken

    fields = ['badge', 'issued', 'active', 'comment', 'redeemable_count', 'users_list']
    readonly_fields = ['issued', 'users_list']
    inlines = [
        BadgeTokenTimeConstraintInline,
    ]

    list_display = ['__str__', 'valid', 'active', 'comment', 'redeemable_count', 'redeemed_count', 'time_constraints_list']
    list_display_links = ['__str__']

    def valid(self, obj):
        return obj.valid

    valid.boolean = True

    def time_constraints_list(self, obj):
        return ','.join([str(k.date_time_range) for k in obj.time_constraints.all()])

    def redeemed_count(self, obj):
        return obj.users.count()

    def users_list(self, obj):
        return ','.join([str(k.user) for k in obj.users.all()])

    def get_readonly_fields(self, request, obj=None):
        if obj:  # editing an existing object
            return [*self.readonly_fields, 'badge']
        return self.readonly_fields


class EventAttachmentInline(admin.TabularInline):
    model = EventAttachment
    fields = ['file', 'filename', 'mime_type', 'visibility']
    extra = 1


class EventParticipantInline(admin.TabularInline):
    model = EventParticipant
    fields = ['participant', 'role', 'is_accepted', 'is_public', 'order']
    extra = 1
    raw_id_fields = ['participant']


class IsImportedListFilter(admin.SimpleListFilter):
    title = _('Event__is_imported')

    parameter_name = 'imported'

    def lookups(self, request, model_admin):
        return [
            (True, _('Event__imported')),
            (False, _('Event__not_imported')),
        ]

    def queryset(self, request, queryset):
        """
        Returns the filtered queryset based on the value
        provided in the query string and retrievable via
        `self.value()`.
        """
        if self.value() == 'True':
            return queryset.filter(
                id__in=ScheduleSourceMapping.objects.filter(
                    mapping_type=ScheduleSourceMapping.MappingType.EVENT,
                )
                .exclude(skip=True)
                .values_list('local_id')
            )
        elif self.value() == 'False':
            return queryset.exclude(
                id__in=ScheduleSourceMapping.objects.filter(
                    mapping_type=ScheduleSourceMapping.MappingType.EVENT,
                )
                .exclude(skip=True)
                .values_list('local_id')
            )


class EventAdmin(admin.ModelAdmin):
    list_display = [
        'conference',
        'track',
        'name',
        'is_public',
        'kind',
        'room',
        'schedule_start',
        'schedule_duration',
        'get_is_imported',
    ]
    list_display_links = ['name']
    list_filter = ['conference', 'track', 'is_public', 'kind', 'room', IsImportedListFilter]
    search_fields = ['name', 'abstract', 'description_de', 'description_en']
    inlines = [
        TagsInline,
        EventAttachmentInline,
        EventParticipantInline,
    ]
    readonly_fields = ['id', 'conference', 'get_is_imported']

    fieldsets = (
        (
            'Organisation',
            {'fields': ['id', 'conference', 'track', 'assembly', 'owner', 'room', 'location', 'kind', 'is_public', 'recording', 'streaming']},
        ),
        (
            'Schedule',
            {'fields': ['schedule_start', 'schedule_duration', 'get_is_imported']},
        ),
        (
            'Data',
            {'fields': ['name', 'slug', 'language', 'abstract', 'description_de', 'description_en', 'banner_image', 'banner_image_url', 'additional_data']},
        ),
    )

    def get_queryset(self, request: HttpRequest) -> QuerySet[Event]:
        return super().get_queryset(request).select_related('conference', 'track', 'room')

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        event_id = request.resolver_match.kwargs.get('object_id')
        if event_id is not None:
            event = Event.objects.get(id=event_id)

            # room field may only list assemblies from the same conference
            if db_field.name == 'room':
                kwargs['queryset'] = Room.objects.filter(conference=event.conference)

            # track field may only list assemblies from the same conference
            if db_field.name == 'track':
                kwargs['queryset'] = ConferenceTrack.objects.filter(conference=event.conference)

        return super().formfield_for_foreignkey(db_field, request, **kwargs)

    def get_fieldsets(self, request, obj=None, **kwargs):
        if obj is None:
            return [
                (
                    'Organisation',
                    {'fields': ['id', 'conference', 'assembly']},
                ),
                (
                    'Data',
                    {'fields': ['name']},
                ),
            ]
        return super().get_fieldsets(request, obj, **kwargs)

    def get_inline_instances(self, request, obj=None, **kwargs):
        # no inlines on new instances
        if obj is None:
            return []
        return super().get_inline_instances(request, obj, **kwargs)

    def get_readonly_fields(self, request, obj=None, **kwargs):
        # upon creation the user may select a conference
        if obj is None:
            return ['id']
        return super().get_readonly_fields(request, obj, **kwargs)

    def get_is_imported(self, instance):
        return instance.is_imported

    get_is_imported.boolean = True
    get_is_imported.short_description = _('Event__is_imported')


class RoomLinkInline(admin.TabularInline):
    model = RoomLink
    list_display = ['link_type', 'name', 'link']


class RoomShareInline(admin.TabularInline):
    model = RoomShare
    list_display = ['timeframe', 'all_assemblies', 'all_self_organized_sessions', 'single_assembly']
    extra = 1


class RoomShareAdmin(admin.ModelAdmin):
    list_display = ['room', 'timeframe', 'enabled', 'all_assemblies', 'all_self_organized_sessions', 'single_assembly']
    list_filter = ['room', 'enabled', 'all_assemblies', 'all_self_organized_sessions', 'single_assembly']


class RoomAdmin(admin.ModelAdmin):
    list_display = ['conference', 'assembly', 'name', 'room_type', 'blocked']
    list_display_links = ['name']
    list_filter = ['conference', 'room_type', 'backend_status', 'blocked', 'is_official', 'is_public_fahrplan']
    search_fields = ['assembly__name', 'name', 'slug']
    inlines = [RoomLinkInline, RoomShareInline, TagsInline]
    readonly_fields = ['id', 'conference', 'occupants', 'reserve_capacity']
    ordering = ('-conference__id', F('assembly__is_official').desc(nulls_last=True), 'assembly__name', F('capacity').desc(nulls_last=True), 'name')

    fieldsets = (
        (
            'Metadata',
            {'fields': ['id', 'conference', 'assembly']},
        ),
        (
            'Schedule',
            {'fields': ['is_official', 'official_room_order', 'is_public_fahrplan', 'recording_state', 'blocked', 'reserve_capacity']},
        ),
        (
            'Data',
            {'fields': ['name', 'room_type', 'capacity', 'occupants', 'description']},
        ),
        (
            'Backend',
            {'fields': ['backend_link', 'backend_link_branch', 'backend_status', 'backend_data', 'director_data']},
        ),
        (
            'Image',
            {'fields': ['banner_image']},
        ),
    )

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        # assembly field may only list assemblies from the same conference
        if db_field.name == 'assembly':
            try:
                room_id = request.resolver_match.kwargs['object_id']
                room = Room.objects.get(id=room_id)
                kwargs['queryset'] = Assembly.objects.filter(conference=room.conference)
            except Exception:
                pass

        return super().formfield_for_foreignkey(db_field, request, **kwargs)

    def get_fieldsets(self, request, obj=None, **kwargs):
        if obj is None:
            return [
                (
                    'Organisation',
                    {'fields': ['id', 'conference', 'assembly', 'blocked']},
                ),
                (
                    'Data',
                    {'fields': ['name', 'room_type', 'backend_link', 'capacity']},
                ),
                (
                    'Image',
                    {'fields': ['banner_image']},
                ),
            ]
        return super().get_fieldsets(request, obj, **kwargs)

    def get_inline_instances(self, request, obj=None, **kwargs):
        # no inlines on new instances
        if obj is None:
            return []
        return super().get_inline_instances(request, obj, **kwargs)

    def get_readonly_fields(self, request, obj=None, **kwargs):
        # upon creation the user may select a conference
        if obj is None:
            return ['id']
        return super().get_readonly_fields(request, obj, **kwargs)


class LinkAdmin(GenericTabularInline):
    model = Link
    extra = 1


class IsSelfOrganized(admin.SimpleListFilter):
    # Human-readable title which will be displayed in the
    # right admin sidebar just above the filter options.
    title = _('is_self_organized')

    # Parameter for the filter that will be used in the URL query.
    parameter_name = 'is_self_organized_filter'

    def lookups(self, request, model_admin):
        return (('True', _('Self-Organized')), ('False', _('Assembly')))

    def queryset(self, request, queryset):
        if self.value() == 'True':
            return queryset.filter(assembly__isnull=True)
        elif self.value() == 'False':
            return queryset.filter(assembly__isnull=False)


class ProjectAdmin(admin.ModelAdmin):
    model = Project
    list_display = ['conference', 'assembly_owner', 'name', 'is_self_organized', 'is_public', 'blocked']
    list_display_links = ['name']
    list_filter = ['conference', 'is_public', IsSelfOrganized]
    search_fields = ['assembly__name', 'owner__username', 'name']
    inlines = [LinkAdmin]
    readonly_fields = ['id', 'conference', 'is_self_organized']
    ordering = ('-conference__id', 'name')

    fieldsets = (
        (
            'Organisation',
            {
                'fields': ['id', 'conference', 'assembly', 'owner', 'is_public', 'blocked', 'is_self_organized'],
                'description': 'Assembly and owner are mutually exclusive. If the owner is set it will be a self-organized project.',
            },
        ),
        ('Data', {'fields': ['name', 'description', 'location']}),
        ('Image', {'fields': ['banner_image']}),
    )

    def get_queryset(self, request: HttpRequest) -> QuerySet[Project]:
        return super().get_queryset(request).select_related('owner', 'assembly', 'conference')

    def is_self_organized(self, instance):
        return instance.is_self_organized

    is_self_organized.boolean = True
    is_self_organized.short_description = 'is self-organized'

    def assembly_owner(self, obj) -> str:
        if obj.owner:
            return mark_safe(f'<a href="{reverse("admin:core_platformuser_change", args=(obj.owner.pk,))}">{obj.owner.username}</a>')
        return mark_safe(f'<a href="{reverse("admin:core_assembly_change", args=(obj.assembly.pk,))}">{obj.assembly.name}</a>')

    def get_fieldsets(self, request, obj=None, **kwargs):
        fielsets = super().get_fieldsets(request, obj, **kwargs)
        if obj is None:
            fielsets[0][1]['fields'].remove('is_self_organized')
            fielsets[0][1]['fields'].remove('blocked')
        return fielsets

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        # assembly field may only list assemblies from the same conference
        if db_field.name == 'assembly':
            try:
                room_id = request.resolver_match.kwargs['object_id']
                room = Room.objects.get(id=room_id)
                kwargs['queryset'] = Assembly.objects.filter(conference=room.conference)
            except Exception:
                pass

        return super().formfield_for_foreignkey(db_field, request, **kwargs)

    def get_inline_instances(self, request, obj=None, **kwargs):
        # no inlines on new instances
        if obj is None:
            return []
        return super().get_inline_instances(request, obj, **kwargs)

    def get_readonly_fields(self, request, obj=None, **kwargs):
        # upon creation the user may select a conference
        if obj is None:
            return ['id']
        return super().get_readonly_fields(request, obj, **kwargs)


class StaticPageRevisionInline(admin.TabularInline):
    model = StaticPageRevision
    fields = ['revision', 'timestamp', 'author', 'is_draft', 'title']
    readonly_fields = fields
    extra = 0
    show_change_link = True
    can_add = False
    can_delete = False


class StaticPageAdmin(admin.ModelAdmin):
    list_display = ['conference', 'language', 'slug', 'protection', 'privacy', 'remove_html', 'sanitize_html', 'title']
    list_display_links = ['slug']
    list_filter = ['conference', 'protection', 'privacy', 'remove_html', 'sanitize_html']
    search_fields = ['title', 'body']
    inlines = [StaticPageRevisionInline]
    readonly_fields = ['id', 'conference', 'language', 'body_html', 'search_content']

    fieldsets = (
        (
            'Organisation',
            {
                'fields': ['id', 'conference', 'slug', 'language'],
            },
        ),
        (
            'Configuration',
            {
                'fields': ['public_revision', 'protection', 'privacy', 'remove_html', 'sanitize_html'],
            },
        ),
        (
            'Data',
            {
                'fields': ['title', ('search_content', 'body_html')],
            },
        ),
    )

    def get_inline_instances(self, request, obj=None, **kwargs):
        # no inlines on new instances
        if obj is None:
            return []
        return super().get_inline_instances(request, obj, **kwargs)

    def get_readonly_fields(self, request, obj=None, **kwargs):
        flds = super().get_readonly_fields(request, obj, **kwargs)
        if obj is None:
            # upon creation the user may select a conference
            flds = [x for x in flds if x not in ['conference']]
        return flds


class StaticPageNamespaceAdmin(admin.ModelAdmin):
    list_display = ['prefix', 'groups', 'conference']
    list_display_links = ['prefix']
    list_filter = ['conference', ('groups', ArrayFieldEntryFilter)]
    search_fields = ['prefix']
    readonly_fields = ['id', 'conference']

    fieldsets = (
        (
            'Organisation',
            {
                'fields': ['id', 'conference'],
            },
        ),
        (
            'Namespace',
            {
                'fields': ['prefix', 'groups'],
            },
        ),
        (
            'Upstream',
            {
                'fields': ['upstream_url', 'upstream_image_base_url'],
            },
        ),
    )

    def get_readonly_fields(self, request, obj=None, **kwargs):
        flds = super().get_readonly_fields(request, obj, **kwargs)
        if obj is None:
            # upon creation the user may select a conference
            flds = [x for x in flds if x not in ['conference']]
        return flds


class StaticPageRevisionAdmin(admin.ModelAdmin):
    list_display = ['conference_page_slugs', 'revision', 'timestamp', 'author', 'is_draft', 'title']
    list_display_links = ['revision']
    list_filter = ['page__conference', 'author', 'is_draft', 'timestamp']
    search_fields = ['title', 'body']
    readonly_fields = ['page', 'revision']
    actions = ['set_revisions_public']

    def set_revisions_public(self, request, queryset):
        for rev in queryset.select_related('page').all():
            logger.info('Revision %s was published by %s', rev, request.user)
            rev.set_public()

    def conference_page_slugs(self, obj):
        return f'{obj.page.conference.slug}/{obj.page.slug}'

    def get_readonly_fields(self, request, obj=None, **kwargs):
        # upon creation the user may select a conference
        if obj is None:
            return ['page']
        return super().get_readonly_fields(request, obj, **kwargs)


class LockAdmin(admin.ModelAdmin):
    list_display = ['uuid', 'content_type', 'object_id']
    search_fields = ['uuid', 'object_id']


class VoucherAdmin(admin.ModelAdmin):
    list_display = ['name', 'enabled', 'assignment', 'target', 'entries', 'show_always', 'hide_from_staff']
    list_filter = ['enabled', 'show_always', 'hide_from_staff', 'target', 'assignment']
    search_fields = ['name', 'description']

    def entries(self, instance):
        return instance.entries.count()


class VoucherEntryAdmin(admin.ModelAdmin):
    list_display = ['id', 'voucher', 'content', 'is_assigned', 'target']
    list_filter = ['voucher', 'assigned']
    search_fields = ['voucher__name', 'content']

    def is_assigned(self, instance):
        return instance.assigned is not None

    is_assigned.boolean = True


class UserDereferrerAllowlistAdmin(admin.ModelAdmin):
    list_display = ['domain', 'user', 'active']
    list_display_links = ['domain']
    search_fields = ['url', 'user']
    readonly_fields = ['id']


class DereferrerStatsAdmin(admin.ModelAdmin):
    list_display = ['domain', 'hits']
    list_display_links = ['domain']
    search_fields = ['domain', 'hits']
    readonly_fields = ['id']


class BulletinBoardEntryAdmin(admin.ModelAdmin):
    list_display = ['conference', 'owner', 'is_public', 'timestamp', 'title']
    fields = ['conference', 'owner', 'is_public', 'title', 'text']
    readonly = ['timestamp']


class ScheduleSourceAdmin(admin.ModelAdmin):
    list_display = ['conference', 'assembly', 'id', 'import_type', 'import_url']
    list_display_links = ['id']
    list_filter = ['conference', 'import_type']

    def has_add_permission(self, request, *args, **kwargs):
        return request.user.is_superuser


class ScheduleSourceImportAdmin(admin.ModelAdmin):
    list_display = ['id', 'schedule_source', 'state', 'summary', 'start', 'end']
    list_display_links = ['id']
    list_filter = ['schedule_source', 'state']

    def has_add_permission(self, *args, **kwargs):
        return settings.DEBUG

    def has_change_permission(self, *args, **kwargs):
        return settings.DEBUG


class ScheduleSourceMappingAdmin(admin.ModelAdmin):
    list_display = ['id', 'schedule_source', 'mapping_type', 'source_id', 'local_id', 'skip']
    list_display_links = ['id']
    list_filter = ['schedule_source', 'mapping_type']
    search_fields = ['source_id', 'local_id']
    readonly_fields = ['schedule_source', 'mapping_type', 'source_id']

    def get_readonly_fields(self, request, obj=None):
        if obj:
            return super().get_readonly_fields(request, obj=obj)
        else:
            return []


class MetaNavItemAdmin(admin.ModelAdmin):
    list_display = ['index', 'enabled', 'slug', 'title', 'conference']
    list_display_links = ['slug', 'title']
    list_filter = ['enabled', 'conference']
    readonly_fields = ['pk', 'conference', 'graphic_light_current', 'graphic_dark_current']

    fieldsets = [
        (
            'Metadata',
            {
                'fields': ['pk', 'conference', 'slug'],
            },
        ),
        (
            'Display',
            {
                'fields': [
                    'index',
                    'visible',
                    'enabled',
                    ('title_de', 'title_en'),
                ],
            },
        ),
        (
            'Item',
            {
                'fields': [
                    'url',
                    ('graphic_light_current', 'graphic_light'),
                    ('graphic_dark_current', 'graphic_dark'),
                ],
            },
        ),
    ]

    def get_readonly_fields(self, request, obj=None, **kwargs):
        result = list(super().get_readonly_fields(request, obj, **kwargs))
        if obj is None:
            result.remove('conference')
        return result

    def graphic_light_current(self, obj: MetaNavItem):
        return obj.get_graphic_light_as_html()

    graphic_light_current.allow_tags = True
    graphic_light_current.caption = _('MetaNavItem__graphic_light')

    def graphic_dark_current(self, obj: MetaNavItem):
        return obj.get_graphic_dark_as_html(default_to_light=False)

    graphic_dark_current.allow_tags = True
    graphic_dark_current.verbose_name = _('MetaNavItem__graphic_dark')


admin.site.site_title = 'Hub Admin'

admin.site.register(PlatformUser, PlatformUserAdmin)

admin.site.register(BulletinBoardEntry, BulletinBoardEntryAdmin)
admin.site.register(Conference, ConferenceAdmin)
admin.site.register(ConferenceNavigationItem, ConferenceNavigationItemAdmin)
admin.site.register(UserDereferrerAllowlist, UserDereferrerAllowlistAdmin)
admin.site.register(DereferrerStats, DereferrerStatsAdmin)
admin.site.register(ConferenceMember, ConferenceMemberAdmin)
admin.site.register(ConferenceTag, ConferenceTagAdmin)
admin.site.register(ConferenceTrack, ConferenceTrackAdmin)
admin.site.register(Assembly, AssemblyAdmin)
admin.site.register(ActivityLogEntry, ActivityLogEntryAdmin)
admin.site.register(BadgeCategory, BadgeCategoryAdmin)
admin.site.register(Badge, BadgeAdmin)
admin.site.register(BadgeToken, BadgeTokenAdmin)
admin.site.register(Event, EventAdmin)
admin.site.register(Invitation, InvitationAdmin)
admin.site.register(Lock, LockAdmin)
admin.site.register(MapFloor, MapFloorAdmin)
admin.site.register(MapPOI, MapPOIAdmin)
admin.site.register(MetaNavItem, MetaNavItemAdmin)
admin.site.register(Project, ProjectAdmin)
admin.site.register(Room, RoomAdmin)
admin.site.register(RoomShare, RoomShareAdmin)
admin.site.register(ScheduleSource, ScheduleSourceAdmin)
admin.site.register(ScheduleSourceImport, ScheduleSourceImportAdmin)
admin.site.register(ScheduleSourceMapping, ScheduleSourceMappingAdmin)
admin.site.register(StaticPage, StaticPageAdmin)
admin.site.register(StaticPageNamespace, StaticPageNamespaceAdmin)
admin.site.register(StaticPageRevision, StaticPageRevisionAdmin)
admin.site.register(Voucher, VoucherAdmin)
admin.site.register(VoucherEntry, VoucherEntryAdmin)