import datetime
import sys
from json import loads

from django import forms
from django.contrib import admin
from django.contrib.admin import register
from django.contrib.admin.decorators import display
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.admin import GenericStackedInline
from django.db.models import Q
from django.db.models.fields.files import FieldFile
from django.forms import BaseInlineFormSet
from django.shortcuts import redirect
from django.template.defaultfilters import linebreaksbr, urlencode
from django.utils.safestring import mark_safe
from mptt.admin import DraggableMPTTAdmin, MPTTAdminForm, TreeRelatedFieldListFilter

from NEMO.actions import (
	access_requests_export_csv,
	adjustment_requests_export_csv,
	create_next_interlock,
	duplicate_tool_configuration,
	lock_selected_interlocks,
	rebuild_area_tree,
	synchronize_with_tool_usage,
	unlock_selected_interlocks,
)
from NEMO.forms import BuddyRequestForm, RecurringConsumableChargeForm, UserPreferencesForm
from NEMO.models import (
	Account,
	AccountType,
	ActivityHistory,
	AdjustmentRequest,
	Alert,
	AlertCategory,
	Area,
	AreaAccessRecord,
	BadgeReader,
	BuddyRequest,
	Chemical,
	ChemicalHazard,
	Closure,
	ClosureTime,
	Comment,
	Configuration,
	ConfigurationHistory,
	Consumable,
	ConsumableCategory,
	ConsumableWithdraw,
	ContactInformation,
	ContactInformationCategory,
	Customization,
	Door,
	EmailLog,
	Interlock,
	InterlockCard,
	InterlockCardCategory,
	LandingPageChoice,
	MembershipHistory,
	News,
	Notification,
	OnboardingPhase,
	PhysicalAccessLevel,
	PhysicalAccessLog,
	Project,
	ProjectDiscipline,
	ProjectDocuments,
	RecurringConsumableCharge,
	RequestMessage,
	Reservation,
	ReservationQuestions,
	Resource,
	ResourceCategory,
	SafetyCategory,
	SafetyIssue,
	SafetyItem,
	SafetyItemDocuments,
	SafetyTraining,
	ScheduledOutage,
	ScheduledOutageCategory,
	StaffAbsence,
	StaffAbsenceType,
	StaffAvailability,
	StaffAvailabilityCategory,
	StaffCharge,
	Task,
	TaskCategory,
	TaskHistory,
	TaskImages,
	TaskStatus,
	TemporaryPhysicalAccess,
	TemporaryPhysicalAccessRequest,
	Tool,
	ToolDocuments,
	ToolQualificationGroup,
	ToolUsageCounter,
	TrainingSession,
	UsageEvent,
	User,
	UserDocuments,
	UserPreferences,
	UserType,
	record_active_state,
	record_local_many_to_many_changes,
	record_remote_many_to_many_changes_and_save,
)
from NEMO.utilities import admin_get_item, format_daterange
from NEMO.views.constants import NEXT_PARAMETER_NAME
from NEMO.views.customization import ProjectsAccountsCustomization
from NEMO.widgets.dynamic_form import DynamicForm, PostUsageFloatFieldQuestion, PostUsageNumberFieldQuestion


# Admin class to allow redirect after add or change
class ModelAdminRedirect(admin.ModelAdmin):

	def response_post_save_add(self, request, obj):
		return self.response_redirect(request, super().response_post_save_add(request, obj))

	def response_post_save_change(self, request, obj):
		return self.response_redirect(request, super().response_post_save_change(request, obj))

	def response_delete(self, request, obj_display, obj_id):
		return self.response_redirect(request, super().response_delete(request, obj_display, obj_id))

	def response_redirect(self, request, original_response):
		if NEXT_PARAMETER_NAME in request.GET:
			return redirect(request.GET[NEXT_PARAMETER_NAME])
		return original_response


# Formset to require at least one inline form
class AtLeastOneRequiredInlineFormSet(BaseInlineFormSet):
	def clean(self):
		super().clean()
		if any(self.errors):
			return
		if not any(cleaned_data and not cleaned_data.get("DELETE", False) for cleaned_data in self.cleaned_data):
			raise forms.ValidationError("A minimum of one item is required.")


class ToolAdminForm(forms.ModelForm):
	class Meta:
		model = Tool
		fields = "__all__"

	class Media:
		js = ("admin/tool/tool.js", "admin/questions_preview/questions_preview.js")
		css = {"": ("admin/questions_preview/questions_preview.css",)}

	qualified_users = forms.ModelMultipleChoiceField(
		queryset=User.objects.all(),
		required=False,
		widget=FilteredSelectMultiple(verbose_name="Users", is_stacked=False),
	)

	required_resources = forms.ModelMultipleChoiceField(
		queryset=Resource.objects.all(),
		required=False,
		widget=FilteredSelectMultiple(verbose_name="Required resources", is_stacked=False),
	)

	nonrequired_resources = forms.ModelMultipleChoiceField(
		queryset=Resource.objects.all(),
		required=False,
		widget=FilteredSelectMultiple(verbose_name="Nonrequired resources", is_stacked=False),
	)

	_tool_calendar_color = forms.CharField(
		required=False, max_length=9, initial="#33ad33", widget=forms.TextInput(attrs={"type": "color"})
	)

	def __init__(self, *args, **kwargs):
		super(ToolAdminForm, self).__init__(*args, **kwargs)
		# Limit interlock selection to ones not already linked (make sure to include current one)
		self.fields["_interlock"].queryset = Interlock.objects.filter(
			Q(id=self.instance._interlock_id) | Q(tool__isnull=True, door__isnull=True))
		if self.instance.pk:
			self.fields["qualified_users"].initial = self.instance.user_set.all()
			self.fields["required_resources"].initial = self.instance.required_resource_set.all()
			self.fields["nonrequired_resources"].initial = self.instance.nonrequired_resource_set.all()

	def clean(self):
		cleaned_data = super().clean()
		image = cleaned_data.get("_image")

		# only resize if an image is present and has changed
		if image and not isinstance(image, FieldFile):
			from NEMO.utilities import resize_image

			# resize image to 500x500 maximum
			cleaned_data["_image"] = resize_image(image, 500)

		return cleaned_data


class ToolDocumentsInline(admin.TabularInline):
	model = ToolDocuments
	extra = 1


@register(Tool)
class ToolAdmin(admin.ModelAdmin):
	inlines = [ToolDocumentsInline]
	list_display = (
		"name_display",
		"_category",
		"visible",
		"operational_display",
		"problematic",
		"is_configurable",
		"has_post_usage_questions",
		"id",
	)
	filter_horizontal = ("_backup_owners", "_superusers")
	search_fields = ("name", "_description", "_serial")
	list_filter = ("visible", "_operational", "_category", "_location", ("_requires_area_access", admin.RelatedOnlyFieldListFilter))
	readonly_fields = ("_post_usage_preview",)
	actions = [duplicate_tool_configuration]
	form = ToolAdminForm
	fieldsets = (
		(
			None,
			{
				"fields": (
					"name",
					"parent_tool",
					"_category",
					"qualified_users",
					"_qualifications_never_expire",
					"_post_usage_questions",
					"_post_usage_preview",
				)
			},
		),
		("Additional Information", {"fields": ("_description", "_serial", "_image", "_tool_calendar_color")}),
		("Current state", {"fields": ("visible", "_operational")}),
		(
			"Contact information",
			{
				"fields": (
					"_primary_owner",
					"_backup_owners",
					"_superusers",
					"_notification_email_address",
					"_location",
					"_phone_number",
				)
			},
		),
		("Reservation", {"fields": ("_reservation_horizon", "_missed_reservation_threshold")}),
		(
			"Usage policy",
			{
				"fields": (
					"_policy_off_between_times",
					"_policy_off_start_time",
					"_policy_off_end_time",
					"_policy_off_weekend",
					"_minimum_usage_block_time",
					"_maximum_usage_block_time",
					"_maximum_reservations_per_day",
					"_minimum_time_between_reservations",
					"_maximum_future_reservation_time",
				)
			},
		),
		(
			"Area Access",
			{
				"fields": (
					"_requires_area_access",
					"_grant_physical_access_level_upon_qualification",
					"_grant_badge_reader_access_upon_qualification",
					"_interlock",
					"_allow_delayed_logoff",
				)
			},
		),
		("Dependencies", {"fields": ("required_resources", "nonrequired_resources")}),
	)

	@admin.display(description="Questions", ordering="post_usage_questions", boolean=True)
	def has_post_usage_questions(self, obj: Tool):
		return True if obj.post_usage_questions else False

	def _post_usage_preview(self, obj):
		if obj.id:
			form_validity_div = '<div id="form_validity"></div>' if obj.post_usage_questions else ""
			return mark_safe(
				'<div class="questions_preview">{}{}</div><div class="help questions_preview_help">Save form to preview post usage questions</div>'.format(
					DynamicForm(obj.post_usage_questions).render("tool_usage_group_question", obj.id), form_validity_div
				)
			)

	def formfield_for_foreignkey(self, db_field, request, **kwargs):
		""" We only want non children tool to be eligible as parents """
		if db_field.name == "parent_tool":
			kwargs["queryset"] = Tool.objects.filter(parent_tool__isnull=True)
		return super().formfield_for_foreignkey(db_field, request, **kwargs)

	def save_model(self, request, obj, form, change):
		"""
		Explicitly record any project membership changes on non-child tools.
		"""
		if obj.parent_tool:
			super(ToolAdmin, self).save_model(request, obj, form, change)
		else:
			record_remote_many_to_many_changes_and_save(
				request, obj, form, change, "qualified_users", super(ToolAdmin, self).save_model
			)
			if "required_resources" in form.changed_data:
				obj.required_resource_set.set(form.cleaned_data["required_resources"])
			if "nonrequired_resources" in form.changed_data:
				obj.nonrequired_resource_set.set(form.cleaned_data["nonrequired_resources"])


@register(ToolQualificationGroup)
class ToolQualificationGroup(admin.ModelAdmin):
	list_display = ["name", "get_tools"]
	filter_horizontal = ["tools"]

	@admin.display(description="Tools", ordering="tools")
	def get_tools(self, obj: ToolQualificationGroup):
		return mark_safe("<br>".join([str(tool) for tool in obj.tools.all()]))


class AreaAdminForm(MPTTAdminForm):
	class Meta:
		model = Area
		fields = "__all__"

	area_calendar_color = forms.CharField(
		required=False, max_length=9, initial="#88B7CD", widget=forms.TextInput(attrs={"type": "color"})
	)


@register(Area)
class AreaAdmin(DraggableMPTTAdmin):
	list_display = (
		"tree_actions",
		"indented_title",
		"name",
		"parent_area",
		"category",
		"requires_reservation",
		"maximum_capacity",
		"reservation_warning",
		"buddy_system_allowed",
		"id",
	)
	fieldsets = (
		(None, {"fields": ("name", "parent_area", "category", "reservation_email", "abuse_email")}),
		("Additional Information", {"fields": ("area_calendar_color",)}),
		(
			"Area access",
			{"fields": ("requires_reservation", "logout_grace_period", "welcome_message", "buddy_system_allowed")},
		),
		(
			"Occupancy",
			{
				"fields": (
					"maximum_capacity",
					"count_staff_in_occupancy",
					"count_service_personnel_in_occupancy",
					"reservation_warning",
				)
			},
		),
		("Reservation", {"fields": ("reservation_horizon", "missed_reservation_threshold")}),
		(
			"Policy",
			{
				"fields": (
					"policy_off_between_times",
					"policy_off_start_time",
					"policy_off_end_time",
					"policy_off_weekend",
					"minimum_usage_block_time",
					"maximum_usage_block_time",
					"maximum_reservations_per_day",
					"minimum_time_between_reservations",
					"maximum_future_reservation_time",
				)
			},
		),
	)
	list_display_links = ("indented_title",)
	list_filter = ("requires_reservation", ("parent_area", TreeRelatedFieldListFilter))
	search_fields = ("name",)
	actions = [rebuild_area_tree]

	mptt_level_indent = 20
	form = AreaAdminForm

	def get_fieldsets(self, request, obj: Area = None):
		"""
		Remove some fieldsets if this area is a parent
		"""
		if obj and not obj.is_leaf_node():
			return [i for i in self.fieldsets if i[0] not in ["Area access", "Reservation", "Policy"]]
		return super().get_fieldsets(request, obj)

	def save_model(self, request, obj: Area, form, change):
		parent_area: Area = obj.parent_area
		if parent_area:
			# if this area has a parent, that parent needs to be cleaned and updated
			parent_area.is_now_a_parent()
		super(AreaAdmin, self).save_model(request, obj, form, change)


@register(TrainingSession)
class TrainingSessionAdmin(admin.ModelAdmin):
	list_display = ("id", "trainer", "trainee", "tool", "project", "type", "date", "duration", "qualified")
	list_filter = ("qualified", "date", "type", ("tool", admin.RelatedOnlyFieldListFilter), ("project", admin.RelatedOnlyFieldListFilter), ("trainer", admin.RelatedOnlyFieldListFilter), ("trainee", admin.RelatedOnlyFieldListFilter))
	date_hierarchy = "date"

	def formfield_for_foreignkey(self, db_field, request, **kwargs):
		""" We only want staff user and tool superusers to be possible trainers """
		if db_field.name == "trainer":
			kwargs["queryset"] = User.objects.filter(Q(is_staff=True) | Q(superuser_for_tools__isnull=False)).distinct()
		return super().formfield_for_foreignkey(db_field, request, **kwargs)


@register(StaffCharge)
class StaffChargeAdmin(ModelAdminRedirect):
	list_display = ("id", "staff_member", "customer", "start", "end")
	list_filter = ("start", ("customer", admin.RelatedOnlyFieldListFilter), ("staff_member", admin.RelatedOnlyFieldListFilter))
	date_hierarchy = "start"


@register(AreaAccessRecord)
class AreaAccessRecordAdmin(ModelAdminRedirect):
	list_display = ("id", "customer", "area", "project", "start", "end")
	list_filter = (("area", TreeRelatedFieldListFilter), "start")
	date_hierarchy = "start"


@register(Configuration)
class ConfigurationAdmin(admin.ModelAdmin):
	list_display = (
		"id",
		"tool",
		"name",
		"enabled",
		"qualified_users_are_maintainers",
		"display_order",
		"exclude_from_configuration_agenda",
	)
	filter_horizontal = ("maintainers",)


@register(ConfigurationHistory)
class ConfigurationHistoryAdmin(admin.ModelAdmin):
	list_display = ("id", "configuration", "user", "modification_time", "slot")
	date_hierarchy = "modification_time"


@register(Account)
class AccountAdmin(admin.ModelAdmin):
	list_display = ("name", "id", "active", "type", "start_date")
	search_fields = ("name",)
	list_filter = ("active", ("type", admin.RelatedOnlyFieldListFilter), "start_date")

	def save_model(self, request, obj, form, change):
		""" Audit account and project active status. """
		super(AccountAdmin, self).save_model(request, obj, form, change)
		record_active_state(request, obj, form, "active", not change)


class ProjectAdminForm(forms.ModelForm):
	class Meta:
		model = Project
		fields = "__all__"

	members = forms.ModelMultipleChoiceField(
		queryset=User.objects.all(),
		required=False,
		widget=FilteredSelectMultiple(verbose_name="Users", is_stacked=False),
	)

	principal_investigators = forms.ModelMultipleChoiceField(
		queryset=User.objects.all(),
		required=False,
		widget=FilteredSelectMultiple(verbose_name="Principal investigators", is_stacked=False),
	)

	def __init__(self, *args, **kwargs):
		super(ProjectAdminForm, self).__init__(*args, **kwargs)
		self.fields["application_identifier"].label = ProjectsAccountsCustomization.get("project_application_identifier_name")
		if self.instance.pk:
			self.fields["members"].initial = self.instance.user_set.all()
			self.fields["principal_investigators"].initial = self.instance.manager_set.all()


class ProjectDocumentsInline(admin.TabularInline):
	model = ProjectDocuments
	extra = 1


@register(Project)
class ProjectAdmin(admin.ModelAdmin):
	list_display = ("name", "id", "get_application_identifier", "account", "active", "start_date")
	filter_horizontal = ("only_allow_tools",)
	search_fields = ("name", "application_identifier", "account__name")
	list_filter = ("active", ("account", admin.RelatedOnlyFieldListFilter), "start_date")
	inlines = [ProjectDocumentsInline]
	form = ProjectAdminForm

	@display(ordering="application_identifier")
	def get_application_identifier(self, project: Project):
		return project.application_identifier

	def save_model(self, request, obj, form, change):
		"""
		Audit project creation and modification. Also save any project membership changes explicitly.
		"""
		record_remote_many_to_many_changes_and_save(
			request, obj, form, change, "members", super(ProjectAdmin, self).save_model
		)
		# Make a history entry if a project has been moved under an account.
		# This applies to newly created projects and project ownership reassignment.
		if "account" in form.changed_data:
			# Create a membership removal entry for the project if it used to belong to another account:
			if change:
				previous_account = MembershipHistory()
				previous_account.authorizer = request.user
				previous_account.child_content_object = obj
				previous_account.parent_content_object = Account.objects.get(pk=form.initial["account"])
				previous_account.action = MembershipHistory.Action.REMOVED
				previous_account.save()

			# Create a membership addition entry for the project with its current account.
			current_account = MembershipHistory()
			current_account.authorizer = request.user
			current_account.child_content_object = obj
			current_account.parent_content_object = obj.account
			current_account.action = MembershipHistory.Action.ADDED
			current_account.save()

		# Record whether the project is active or not.
		record_active_state(request, obj, form, "active", not change)

		if "principal_investigators" in form.changed_data:
			obj.manager_set.set(form.cleaned_data["principal_investigators"])


@register(Reservation)
class ReservationAdmin(ModelAdminRedirect):
	list_display = (
		"id",
		"user",
		"creator",
		"tool",
		"area",
		"project",
		"start",
		"end",
		"duration",
		"cancelled",
		"missed",
		"shortened",
	)
	readonly_fields = ("descendant",)
	list_filter = ("cancelled", "missed", ("tool", admin.RelatedOnlyFieldListFilter), ("area", TreeRelatedFieldListFilter), ("user", admin.RelatedOnlyFieldListFilter))
	date_hierarchy = "start"


class ReservationQuestionsForm(forms.ModelForm):
	class Meta:
		model = ReservationQuestions
		fields = "__all__"

	class Media:
		js = ("admin/reservation_questions/reservation_questions.js", "admin/questions_preview/questions_preview.js")
		css = {"": ("admin/questions_preview/questions_preview.css",)}

	def clean(self):
		cleaned_data = super().clean()
		reservation_questions = cleaned_data.get("questions")
		tool_reservations = cleaned_data.get("tool_reservations")
		only_tools = cleaned_data.get("only_for_tools")
		area_reservations = cleaned_data.get("area_reservations")
		only_areas = cleaned_data.get("only_for_areas")
		if not tool_reservations and not area_reservations:
			self.add_error("tool_reservations", "Reservation questions have to apply to tool and/or area reservations")
			self.add_error("area_reservations", "Reservation questions have to apply to tool and/or area reservations")
		if not tool_reservations and only_tools:
			self.add_error(
				"tool_reservations", "You cannot restrict tools these questions apply to without enabling it for tools"
			)
		if not area_reservations and only_areas:
			self.add_error(
				"area_reservations", "You cannot restrict areas these questions apply to without enabling it for areas"
			)
		# Validate reservation_questions JSON format
		if reservation_questions:
			try:
				loads(reservation_questions)
			except ValueError:
				self.add_error("questions", "This field needs to be a valid JSON string")
			try:
				dynamic_form = DynamicForm(reservation_questions)
				dynamic_form.validate("reservation_group_question", self.instance.id)
			except KeyError as e:
				self.add_error("questions", f"{e} property is required")
			except Exception:
				error_info = sys.exc_info()
				self.add_error("questions", error_info[0].__name__ + ": " + str(error_info[1]))
		return cleaned_data


@register(ReservationQuestions)
class ReservationQuestionsAdmin(admin.ModelAdmin):
	form = ReservationQuestionsForm
	filter_horizontal = ("only_for_tools", "only_for_areas", "only_for_projects")
	readonly_fields = ("questions_preview",)
	fieldsets = (
		(
			None,
			{
				"fields": (
					"name",
					"questions",
					"questions_preview",
					"tool_reservations",
					"only_for_tools",
					"area_reservations",
					"only_for_areas",
					"only_for_projects",
				)
			},
		),
	)

	def questions_preview(self, obj):
		form_validity_div = ""
		rendered_form = ""
		try:
			rendered_form = DynamicForm(obj.questions).render("reservation_group_question", obj.id)
			if obj.questions:
				form_validity_div = '<div id="form_validity"></div>'
		except:
			pass
		return mark_safe(
			'<div class="questions_preview">{}{}</div><div class="help questions_preview_help">Save form to preview reservation questions</div>'.format(
				rendered_form, form_validity_div
			)
		)


@register(UsageEvent)
class UsageEventAdmin(ModelAdminRedirect):
	list_display = ("id", "tool", "user", "operator", "project", "start", "end", "duration", "remote_work")
	list_filter = ("remote_work", "start", "end", ("tool", admin.RelatedOnlyFieldListFilter))
	date_hierarchy = "start"


@register(Consumable)
class ConsumableAdmin(admin.ModelAdmin):
	list_display = ("name", "quantity", "category", "visible", "reusable", "reminder_threshold", "reminder_email", "id")
	list_filter = ("visible", ("category", admin.RelatedOnlyFieldListFilter), "reusable")
	search_fields = ("name",)
	readonly_fields = ("reminder_threshold_reached",)


@register(ConsumableCategory)
class ConsumableCategoryAdmin(admin.ModelAdmin):
	list_display = ("name",)


@register(ConsumableWithdraw)
class ConsumableWithdrawAdmin(admin.ModelAdmin):
	list_display = ("id", "customer", "merchant", "consumable", "quantity", "project", "date")
	list_filter = ("date", ("consumable", admin.RelatedOnlyFieldListFilter))
	date_hierarchy = "date"


@register(RecurringConsumableCharge)
class RecurringConsumableChargeAdmin(admin.ModelAdmin):
	form = RecurringConsumableChargeForm
	list_display = ("name", "customer", "project", "get_recurrence_display", "last_charge", "next_charge")
	list_filter = (("customer", admin.RelatedOnlyFieldListFilter),)
	readonly_fields = ("last_charge", "last_updated", "last_updated_by")

	def save_model(self, request, obj: RecurringConsumableCharge, form, change):
		obj.save_with_user(request.user)


class InterlockCardAdminForm(forms.ModelForm):
	class Meta:
		model = InterlockCard
		widgets = {"password": forms.PasswordInput(render_value=True)}
		fields = "__all__"

	def clean(self):
		if any(self.errors):
			return
		cleaned_data = super().clean()
		category = cleaned_data["category"]
		from NEMO import interlocks

		interlocks.get(category, False).clean_interlock_card(self)
		return cleaned_data


@register(InterlockCard)
class InterlockCardAdmin(admin.ModelAdmin):
	form = InterlockCardAdminForm
	list_display = ("name", "enabled", "server", "port", "number", "category", "even_port", "odd_port")


class InterlockAdminForm(forms.ModelForm):
	class Meta:
		model = Interlock
		fields = "__all__"

	def clean(self):
		if any(self.errors):
			return
		cleaned_data = super().clean()
		from NEMO import interlocks

		category = self.cleaned_data["card"].category
		interlocks.get(category, False).clean_interlock(self)
		return cleaned_data


@register(Interlock)
class InterlockAdmin(admin.ModelAdmin):
	form = InterlockAdminForm
	list_display = (
		"id",
		"get_card_enabled",
		"card",
		"channel",
		"unit_id",
		"state",
		"tool",
		"door",
		"most_recent_reply_time"
	)
	list_filter = ("card__enabled", ("card", admin.RelatedOnlyFieldListFilter), "state", ("tool", admin.RelatedOnlyFieldListFilter), ("door", admin.RelatedOnlyFieldListFilter))
	actions = [lock_selected_interlocks, unlock_selected_interlocks, synchronize_with_tool_usage, create_next_interlock]
	readonly_fields = ["state", "most_recent_reply", "most_recent_reply_time"]

	@display(boolean=True, ordering='card__enabled', description='Card Enabled')
	def get_card_enabled(self, obj):
		return obj.card.enabled


@register(InterlockCardCategory)
class InterlockCardCategoryAdmin(admin.ModelAdmin):
	list_display = ("name",)


@register(Task)
class TaskAdmin(admin.ModelAdmin):
	list_display = (
		"id",
		"urgency",
		"tool",
		"creator",
		"creation_time",
		"last_updated",
		"problem_category",
		"cancelled",
		"resolved",
		"resolution_category",
	)
	list_filter = ("urgency", "resolved", "cancelled", "safety_hazard", "creation_time", ("tool", admin.RelatedOnlyFieldListFilter), ("creator", admin.RelatedOnlyFieldListFilter))
	date_hierarchy = "creation_time"


@register(TaskCategory)
class TaskCategoryAdmin(admin.ModelAdmin):
	list_display = ("name", "stage")


@register(TaskStatus)
class TaskStatusAdmin(admin.ModelAdmin):
	list_display = (
		"name",
		"notify_primary_tool_owner",
		"notify_backup_tool_owners",
		"notify_tool_notification_email",
		"custom_notification_email_address",
	)


@register(TaskHistory)
class TaskHistoryAdmin(admin.ModelAdmin):
	list_display = ("id", "task", "status", "time", "user")
	readonly_fields = ("time",)
	date_hierarchy = "time"


@register(TaskImages)
class TaskImagesAdmin(admin.ModelAdmin):
	list_display = ("id", "get_tool", "task", "uploaded_at")

	@admin.display(ordering="tool", description="Tool Name")
	def get_tool(self, task_image: TaskImages):
		return task_image.task.tool.name


@register(Comment)
class CommentAdmin(admin.ModelAdmin):
	list_display = (
		"id",
		"tool",
		"author",
		"creation_date",
		"expiration_date",
		"visible",
		"staff_only",
		"hidden_by",
		"hide_date",
	)
	list_filter = ("visible", "creation_date", ("tool", admin.RelatedOnlyFieldListFilter), "staff_only")
	date_hierarchy = "creation_date"
	search_fields = ("content",)


@register(Resource)
class ResourceAdmin(admin.ModelAdmin):
	list_display = ("name", "category", "available")
	list_filter = ("available", "category")
	filter_horizontal = ("fully_dependent_tools", "partially_dependent_tools", "dependent_areas")


@register(ActivityHistory)
class ActivityHistoryAdmin(admin.ModelAdmin):
	list_display = ("__str__", "get_item", "action", "date", "authorizer")
	list_filter = ("action", ("content_type", admin.RelatedOnlyFieldListFilter),)
	date_hierarchy = "date"

	@admin.display(description="Item")
	def get_item(self, obj: ActivityHistory):
		return admin_get_item(obj.content_type, obj.object_id)


@register(MembershipHistory)
class MembershipHistoryAdmin(admin.ModelAdmin):
	list_display = (
		"__str__",
		"get_parent",
		"action",
		"get_child",
		"date",
		"authorizer",
	)
	list_filter = (("parent_content_type", admin.RelatedOnlyFieldListFilter), ("child_content_type", admin.RelatedOnlyFieldListFilter),)
	date_hierarchy = "date"

	@admin.display(description="Parent")
	def get_parent(self, obj: MembershipHistory):
		return admin_get_item(obj.parent_content_type, obj.parent_object_id)

	@admin.display(description="Child")
	def get_child(self, obj: MembershipHistory):
		return admin_get_item(obj.child_content_type, obj.child_object_id)


@register(UserType)
class UserTypeAdmin(admin.ModelAdmin):
	list_display = ("name",)


@register(UserPreferences)
class UserPreferencesAdmin(admin.ModelAdmin):
	list_display = ("user",)
	search_fields = ["user_preferences__user__first_name", "user_preferences__user__last_name", "user_preferences__user__username"]
	filter_horizontal = ["tool_freed_time_notifications", "tool_adjustment_notifications"]
	form = UserPreferencesForm


class UserAdminForm(forms.ModelForm):
	class Meta:
		model = User
		fields = "__all__"

	tool_qualifications = forms.ModelMultipleChoiceField(
		label="Qualifications",
		queryset=Tool.objects.filter(parent_tool__isnull=True),
		required=False,
		widget=FilteredSelectMultiple(verbose_name="tools", is_stacked=False),
	)

	backup_owner_on_tools = forms.ModelMultipleChoiceField(
		queryset=Tool.objects.filter(parent_tool__isnull=True),
		required=False,
		widget=FilteredSelectMultiple(verbose_name="tools", is_stacked=False),
	)

	superuser_on_tools = forms.ModelMultipleChoiceField(
		queryset=Tool.objects.filter(parent_tool__isnull=True),
		required=False,
		widget=FilteredSelectMultiple(verbose_name="tools", is_stacked=False),
	)

	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		if self.instance.pk:
			self.fields["tool_qualifications"].initial = self.instance.qualifications.all()
			self.fields["backup_owner_on_tools"].initial = self.instance.backup_for_tools.all()
			self.fields["superuser_on_tools"].initial = self.instance.superuser_for_tools.all()


class UserDocumentsInline(admin.TabularInline):
	model = UserDocuments
	extra = 1


@register(User)
class UserAdmin(admin.ModelAdmin):
	form = UserAdminForm
	inlines = [UserDocumentsInline]
	filter_horizontal = (
		"groups",
		"user_permissions",
		"projects",
		"managed_projects",
		"physical_access_levels",
		"onboarding_phases",
		"safety_trainings"
	)
	fieldsets = (
		(
			"Personal information",
			{
				"fields": (
					"first_name",
					"last_name",
					"username",
					"email",
					"badge_number",
					"type",
					"domain",
					"notes"
				)
			},
		),
		(
			"Permissions",
			{
				"fields": (
					"is_active",
					"is_staff",
					"is_user_office",
					"is_accounting_officer",
					"is_service_personnel",
					"is_technician",
					"is_facility_manager",
					"is_superuser",
					"training_required",
					"groups",
					"user_permissions",
					"physical_access_levels",
				)
			},
		),
		("Important dates", {"fields": ("date_joined", "last_login", "access_expiration")}),
		(
			"Facility information",
			{
				"fields": (
					"tool_qualifications",
					"backup_owner_on_tools",
					"superuser_on_tools",
					"projects",
					"managed_projects",
				)
			},
		),
		(
			"Other information",
			{
				"fields": (
					"onboarding_phases",
					"safety_trainings",
				)
			},
		),
	)
	search_fields = ("first_name", "last_name", "username", "email")
	list_display = (
		"username",
		"first_name",
		"last_name",
		"email",
		"is_active",
		"domain",
		"is_staff",
		"is_user_office",
		"is_accounting_officer",
		"is_technician",
		"is_service_personnel",
		"is_facility_manager",
		"is_superuser",
		"date_joined",
		"last_login",
	)
	list_filter = (
		"is_active",
		"domain",
		"is_staff",
		"is_user_office",
		"is_accounting_officer",
		"is_facility_manager",
		"is_superuser",
		"is_technician",
		"is_service_personnel",
		"date_joined",
		"last_login",
	)

	def save_model(self, request, obj, form, change):
		""" Audit project membership and qualifications when a user is saved. """
		super(UserAdmin, self).save_model(request, obj, form, change)
		record_local_many_to_many_changes(request, obj, form, "projects")
		record_local_many_to_many_changes(request, obj, form, "qualifications", "tool_qualifications")
		record_local_many_to_many_changes(request, obj, form, "physical_access_levels")
		record_active_state(request, obj, form, "is_active", not change)
		if "tool_qualifications" in form.changed_data:
			obj.qualifications.set(form.cleaned_data["tool_qualifications"])
		if "backup_owner_on_tools" in form.changed_data:
			obj.backup_for_tools.set(form.cleaned_data["backup_owner_on_tools"])
		if "superuser_on_tools" in form.changed_data:
			obj.superuser_for_tools.set(form.cleaned_data["superuser_on_tools"])


@register(PhysicalAccessLog)
class PhysicalAccessLogAdmin(admin.ModelAdmin):
	list_display = ("user", "door", "time", "result")
	list_filter = (("door", admin.RelatedOnlyFieldListFilter), "result")
	search_fields = ("user__first_name", "user__last_name", "user__username", "door__name")
	date_hierarchy = "time"

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

	def has_add_permission(self, request):
		return False

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


@register(SafetyIssue)
class SafetyIssueAdmin(admin.ModelAdmin):
	list_display = ("id", "reporter", "creation_time", "visible", "resolved", "resolution_time", "resolver")
	list_filter = ("resolved", "visible", "creation_time", "resolution_time", ("reporter", admin.RelatedOnlyFieldListFilter))
	readonly_fields = ("creation_time", "resolution_time")
	search_fields = ("location", "concern", "progress", "resolution")


@register(SafetyCategory)
class SafetyCategoryAdmin(admin.ModelAdmin):
	list_display = ("name", "display_order")


class SafetyItemDocumentsInline(admin.TabularInline):
	model = SafetyItemDocuments
	extra = 1


@register(SafetyItem)
class SafetyItemAdmin(admin.ModelAdmin):
	inlines = [SafetyItemDocumentsInline]
	list_display = ("name", "category", "get_documents_number")
	list_filter = (("category", admin.RelatedOnlyFieldListFilter),)

	@display(description='Documents')
	def get_documents_number(self, obj: SafetyItem):
		return SafetyItemDocuments.objects.filter(safety_item=obj).count()


class DoorAdminForm(forms.ModelForm):
	def __init__(self, *args, **kwargs):
		super(DoorAdminForm, self).__init__(*args, **kwargs)
		# Limit interlock selection to ones not already linked (and exclude current one)
		self.fields["interlock"].queryset = Interlock.objects.filter(
			Q(id=self.instance.interlock_id) | Q(tool__isnull=True, door__isnull=True))


@register(Door)
class DoorAdmin(admin.ModelAdmin):
	list_display = ("name", "area", "interlock", "get_absolute_url", "id")
	form = DoorAdminForm


@register(AlertCategory)
class AlertCategoryAdmin(admin.ModelAdmin):
	list_display = ("name",)


class AlertAdminForm(forms.ModelForm):
	contents = forms.CharField(widget=forms.Textarea(attrs={"rows": 3, "cols": 50}))

	class Meta:
		model = Alert
		fields = "__all__"


@register(Alert)
class AlertAdmin(admin.ModelAdmin):
	list_display = (
		"title",
		"category",
		"creation_time",
		"creator",
		"debut_time",
		"expiration_time",
		"user",
		"dismissible",
		"expired",
		"deleted",
	)
	list_filter = ("category", "dismissible", "expired", "deleted")
	form = AlertAdminForm


class PhysicalAccessLevelForm(forms.ModelForm):
	class Meta:
		model = PhysicalAccessLevel
		fields = "__all__"

	class Media:
		js = ("admin/physical_access_level/access_level.js",)

	authorized_users = forms.ModelMultipleChoiceField(
		queryset=User.objects.all(),
		required=False,
		widget=FilteredSelectMultiple(verbose_name="Users", is_stacked=False),
	)

	closures = forms.ModelMultipleChoiceField(
		queryset=Closure.objects.all(),
		required=False,
		widget=FilteredSelectMultiple(verbose_name="Closures", is_stacked=False),
	)

	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		if self.instance.pk:
			self.fields["authorized_users"].initial = self.instance.user_set.all()
			self.fields["closures"].initial = self.instance.closure_set.all()

	def clean(self):
		cleaned_data = super().clean()
		schedule = cleaned_data.get("schedule")
		if schedule == PhysicalAccessLevel.Schedule.WEEKDAYS:
			start_date = cleaned_data.get("weekdays_start_time")
			end_date = cleaned_data.get("weekdays_end_time")
			if not start_date:
				self.add_error("weekdays_start_time", "Start time is required for weekdays.")
			if not end_date:
				self.add_error("weekdays_end_time", "End time is required for weekdays.")
		else:
			cleaned_data["weekdays_start_time"] = None
			cleaned_data["weekdays_end_time"] = None
		return cleaned_data


@register(PhysicalAccessLevel)
class PhysicalAccessLevelAdmin(admin.ModelAdmin):
	form = PhysicalAccessLevelForm
	list_display = ("name", "area", "get_schedule_display_with_times", "allow_staff_access", "allow_user_request")
	list_filter = (("area", TreeRelatedFieldListFilter), "allow_staff_access", "allow_user_request")

	def save_model(self, request, obj, form, change):
		"""
		Explicitly record any membership changes.
		"""
		record_remote_many_to_many_changes_and_save(request, obj, form, change, "authorized_users", super().save_model)
		if "closures" in form.changed_data:
			obj.closure_set.set(form.cleaned_data["closures"])


class ClosureTimeInline(admin.TabularInline):
	model = ClosureTime
	formset = AtLeastOneRequiredInlineFormSet
	min_num = 1
	extra = 1


class ClosureAdminForm(forms.ModelForm):
	def clean(self):
		if any(self.errors):
			return
		cleaned_data = super().clean()
		alert_template = cleaned_data.get("alert_template")
		alert_days_before = cleaned_data.get("alert_days_before")
		if alert_days_before is not None and not alert_template:
			self.add_error("alert_template", "Please provide an alert message")
		if alert_template:
			if alert_days_before is None:
				self.add_error("alert_days_before", "Please select when the alert should be displayed")
			try:
				validate_closure = Closure()
				validate_closure.name = cleaned_data.get("name")
				validate_closure.alert_template = alert_template
				validate_closure.staff_absent = cleaned_data.get("staff_absent")
				access_levels = cleaned_data.get("physical_access_levels")
				closure_time = ClosureTime()
				closure_time.closure = validate_closure
				closure_time.start_time = datetime.datetime.now()
				closure_time.end_time = datetime.datetime.now()
				closure_time.alert_contents(access_levels=access_levels)
			except Exception as template_exception:
				self.add_error("alert_template", str(template_exception))

	class Meta:
		model = Closure
		fields = "__all__"

	class Media:
		js = ("admin/time_options_override.js",)


@register(Closure)
class ClosureAdmin(admin.ModelAdmin):
	inlines = [ClosureTimeInline]
	form = ClosureAdminForm
	list_display = ("name", "alert_days_before", "get_times_display", "staff_absent", "notify_managers_last_occurrence")
	filter_horizontal = ("physical_access_levels",)
	list_filter = (("physical_access_levels__area", TreeRelatedFieldListFilter), "staff_absent", "notify_managers_last_occurrence")
	readonly_fields = ("alert_preview",)
	fieldsets = (
		(
			None,
			{
				"fields": (
					"name",
					"alert_days_before",
					"alert_template",
					"alert_preview",
					"notify_managers_last_occurrence",
					"staff_absent",
					"physical_access_levels"
				)
			},
		),
	)

	def alert_preview(self, obj: Closure):
		if obj.alert_template and obj.closuretime_set.exists():
			try:
				alert_style = "width: 350px; color: #a94442; background-color: #f2dede; border-color: #dca7a7;padding: 15px; margin-bottom: 10px; border: 1px solid transparent; border-radius: 4px;"
				display_title = f'<span style="font-weight:bold">{obj.name}</span><br>' if obj.name else ''
				return iframe_content(f'<div style="{alert_style}">{display_title}{linebreaksbr(obj.closuretime_set.first().alert_contents())}</div>', extra_style="padding-bottom: 20%")
			except Exception:
				pass
		return ""

	@admin.display(description="Times")
	def get_times_display(self, closure: Closure) -> str:
		return mark_safe("<br>".join([format_daterange(ct.start_time, ct.end_time, dt_format="SHORT_DATETIME_FORMAT", d_format="SHORT_DATE_FORMAT", date_separator=" ", time_separator=" - ") for ct in ClosureTime.objects.filter(closure=closure)]))


class TemporaryPhysicalAccessAdminForm(forms.ModelForm):
	class Meta:
		model = TemporaryPhysicalAccess
		fields = "__all__"

	class Media:
		js = ("admin/time_options_override.js",)


@register(TemporaryPhysicalAccess)
class TemporaryPhysicalAccessAdmin(admin.ModelAdmin):
	list_display = ("id", "user", "start_time", "end_time", "get_area_name", "get_schedule_display_with_times")
	list_filter = (("physical_access_level", admin.RelatedOnlyFieldListFilter), ("physical_access_level__area", TreeRelatedFieldListFilter), "end_time", "start_time", ("user", admin.RelatedOnlyFieldListFilter))
	form = TemporaryPhysicalAccessAdminForm

	@admin.display(ordering="physical_access_level__area", description="Area")
	def get_area_name(self, tpa: TemporaryPhysicalAccess) -> str:
		return tpa.physical_access_level.area.name

	@admin.display(description="Schedule")
	def get_schedule_display_with_times(self, tpa: TemporaryPhysicalAccess) -> str:
		return tpa.physical_access_level.get_schedule_display_with_times()


class TemporaryPhysicalAccessRequestFormAdmin(forms.ModelForm):
	class Meta:
		model = TemporaryPhysicalAccessRequest
		fields = "__all__"


@register(TemporaryPhysicalAccessRequest)
class TemporaryPhysicalAccessRequestAdmin(admin.ModelAdmin):
	form = TemporaryPhysicalAccessRequestFormAdmin
	list_display = (
		"creator",
		"creation_time",
		"other_users_display",
		"start_time",
		"end_time",
		"physical_access_level",
		"status_display",
		"reviewer",
		"deleted"
	)
	list_filter = ("status", "deleted", ("creator", admin.RelatedOnlyFieldListFilter), ("physical_access_level", admin.RelatedOnlyFieldListFilter))
	filter_horizontal = ("other_users",)
	actions = [access_requests_export_csv]

	@admin.display(ordering="other_users", description="Buddies")
	def other_users_display(self, access_request: TemporaryPhysicalAccessRequest):
		return mark_safe("<br>".join([u.username for u in access_request.other_users.all()]))

	@admin.display(ordering="status", description="Status")
	def status_display(self, access_request: TemporaryPhysicalAccessRequest):
		return access_request.get_status_display()


@register(ContactInformationCategory)
class ContactInformationCategoryAdmin(admin.ModelAdmin):
	list_display = ("name", "display_order")


@register(ContactInformation)
class ContactInformationAdmin(admin.ModelAdmin):
	list_display = ("name", "category", "user")


@register(LandingPageChoice)
class LandingPageChoiceAdmin(admin.ModelAdmin):
	list_display = (
		"display_order",
		"name",
		"url",
		"open_in_new_tab",
		"secure_referral",
		"hide_from_mobile_devices",
		"hide_from_desktop_computers",
	)
	list_display_links = ("name",)


@register(Customization)
class CustomizationAdmin(admin.ModelAdmin):
	list_display = ("name", "value")


@register(ScheduledOutageCategory)
class ScheduledOutageCategoryAdmin(admin.ModelAdmin):
	list_display = ("name",)


@register(ScheduledOutage)
class ScheduledOutageAdmin(admin.ModelAdmin):
	list_display = ("id", "tool", "area", "resource", "creator", "title", "start", "end")
	list_filter = (("tool", admin.RelatedOnlyFieldListFilter), ("area", TreeRelatedFieldListFilter), ("resource", admin.RelatedOnlyFieldListFilter), ("creator", admin.RelatedOnlyFieldListFilter))


@register(News)
class NewsAdmin(admin.ModelAdmin):
	list_display = ("id", "title", "created", "last_updated", "archived", "pinned")
	list_filter = ("archived", "pinned")


@register(Notification)
class NotificationAdmin(admin.ModelAdmin):
	list_display = ("id", "user", "get_item", "notification_type", "expiration")
	list_filter = (("content_type", admin.RelatedOnlyFieldListFilter), "notification_type", )

	@admin.display(description="Item")
	def get_item(self, obj: Notification):
		return admin_get_item(obj.content_type, obj.object_id)


@register(BadgeReader)
class BadgeReaderAdmin(admin.ModelAdmin):
	list_display = ("id", "name", "send_key", "record_key")


class CounterAdminForm(forms.ModelForm):
	class Meta:
		model = ToolUsageCounter
		fields = "__all__"

	def clean(self):
		cleaned_data = super().clean()
		tool = cleaned_data.get("tool")
		tool_usage_question_name = cleaned_data.get("tool_usage_question")
		if tool and tool_usage_question_name:
			error = None
			if tool.post_usage_questions:
				post_usage_form = DynamicForm(tool.post_usage_questions)
				tool_question = post_usage_form.filter_questions(
					lambda x: (isinstance(x, PostUsageNumberFieldQuestion) or isinstance(x, PostUsageFloatFieldQuestion)) and x.name == tool_usage_question_name
				)
				if not tool_question:
					candidates = [
						question.name
						for question in post_usage_form.filter_questions(
							lambda x: isinstance(x, PostUsageNumberFieldQuestion) or isinstance(x, PostUsageFloatFieldQuestion)
						)
					]
					error = "The tool has no post usage question of type Number or Float with this name."
					if candidates:
						error += f" Valid question names are: {', '.join(candidates)}"
			else:
				error = "The tool does not have any post usage questions."
			if error:
				self.add_error("tool_usage_question", error)
		return cleaned_data


@register(ToolUsageCounter)
class CounterAdmin(admin.ModelAdmin):
	list_display = (
		"name",
		"tool",
		"tool_usage_question",
		"value",
		"warning_threshold",
		"last_reset",
		"last_reset_by",
		"is_active",
	)
	list_filter = (("tool", admin.RelatedOnlyFieldListFilter), "last_reset")
	readonly_fields = ("warning_threshold_reached",)
	form = CounterAdminForm


class RequestMessageInlines(GenericStackedInline):
	model = RequestMessage
	extra = 1


@register(BuddyRequest)
class BuddyRequestAdmin(admin.ModelAdmin):
	inlines = [RequestMessageInlines]
	form = BuddyRequestForm
	list_display = ("user", "start", "end", "area", "reply_count", "expired", "deleted")
	list_filter = ("expired", "deleted", ("user", admin.RelatedOnlyFieldListFilter), ("area", TreeRelatedFieldListFilter))

	@admin.display(ordering="replies", description="Replies")
	def reply_count(self, buddy_request: BuddyRequest):
		return buddy_request.replies.count()


@register(AdjustmentRequest)
class AdjustmentRequestAdmin(admin.ModelAdmin):
	inlines = [RequestMessageInlines]
	list_display = ("creator", "last_updated", "get_item", "get_time_difference", "get_status_display", "reply_count", "deleted")
	list_filter = ("status", "deleted", ("creator", admin.RelatedOnlyFieldListFilter), ("reviewer", admin.RelatedOnlyFieldListFilter))
	date_hierarchy = "last_updated"
	actions = [adjustment_requests_export_csv]

	@admin.display(description="Diff")
	def get_time_difference(self, adjustment_request: AdjustmentRequest):
		return adjustment_request.get_time_difference()

	@admin.display(ordering="replies", description="Replies")
	def reply_count(self, adjustment_request: AdjustmentRequest):
		return adjustment_request.replies.count()

	@admin.display(description="Item")
	def get_item(self, adjustment_request: AdjustmentRequest):
		return admin_get_item(adjustment_request.item_type, adjustment_request.item_id)


@register(StaffAbsenceType)
class StaffAbsenceTypeAdmin(admin.ModelAdmin):
	list_display = ("name", "description")


@register(StaffAvailabilityCategory)
class StaffAvailabilityCategoryAdmin(admin.ModelAdmin):
	list_display = ("name", "display_order")


@register(StaffAvailability)
class StaffAvailabilityAdmin(admin.ModelAdmin):
	list_display = ("staff_member", "category", "visible", "start_time", "end_time", *StaffAvailability.DAYS)
	list_filter = ("category", *StaffAvailability.DAYS)

	def formfield_for_foreignkey(self, db_field, request, **kwargs):
		""" We only want active users here """
		if db_field.name == "staff_member":
			kwargs["queryset"] = User.objects.filter(is_active=True)
		return super().formfield_for_foreignkey(db_field, request, **kwargs)


@register(StaffAbsence)
class StaffAbsenceAdmin(admin.ModelAdmin):
	list_display = ("creation_time", "staff_member", "absence_type", "full_day", "start_date", "end_date")
	list_filter = (("staff_member", admin.RelatedOnlyFieldListFilter), "absence_type", "start_date", "end_date", "creation_time")


class ChemicalHazardAdminForm(forms.ModelForm):
	class Meta:
		model = ChemicalHazard
		fields = "__all__"

	chemicals = forms.ModelMultipleChoiceField(
		queryset=Chemical.objects.all(),
		required=False,
		widget=FilteredSelectMultiple(verbose_name="Chemicals", is_stacked=False),
	)

	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)
		if self.instance.pk:
			self.fields["chemicals"].initial = self.instance.chemical_set.all()

	def clean(self):
		cleaned_data = super().clean()
		logo = cleaned_data.get("logo")

		# only resize if a logo is present and has changed
		if logo and not isinstance(logo, FieldFile):
			from NEMO.utilities import resize_image

			# resize image to 250x250 maximum
			cleaned_data["logo"] = resize_image(logo, 250)


@register(ChemicalHazard)
class ChemicalHazardAdmin(admin.ModelAdmin):
	form = ChemicalHazardAdminForm
	list_display = ("name", "display_order")

	def save_model(self, request, obj: ChemicalHazard, form, change):
		super().save_model(request, obj, form, change)
		if "chemicals" in form.changed_data:
			obj.chemical_set.set(form.cleaned_data["chemicals"])


@register(Chemical)
class ChemicalAdmin(admin.ModelAdmin):
	filter_horizontal = ("hazards",)
	list_filter = ("hazards",)


@register(SafetyTraining)
class SafetyTrainingAdmin(admin.ModelAdmin):
	list_display = ("name", "display_order")


@register(OnboardingPhase)
class OnboardingPhaseAdmin(admin.ModelAdmin):
	list_display = ("name", "display_order")


@register(EmailLog)
class EmailLogAdmin(admin.ModelAdmin):
	list_display = ["id", "category", "sender", "to", "subject", "when", "ok"]
	list_filter = ["category", "ok"]
	search_fields = ["subject", "content", "to"]
	readonly_fields = ("content_preview",)
	date_hierarchy = "when"

	def content_preview(self, obj):
		if obj.content:
			return iframe_content(obj.content)
		else:
			return ""

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

	def has_add_permission(self, request):
		return False

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


def iframe_content(content, extra_style="padding-bottom: 75%") -> str:
	return mark_safe(f'<div style="position: relative; display: block; overflow: hidden; {extra_style}"><iframe style="position: absolute; width:100%; height:100%; border:none" src="data:text/html,{urlencode(content)}"></iframe></div>')


def has_admin_site_permission(request):
	"""
	Return True if the given HttpRequest has permission to view
	*at least one* page in the admin site.
	In our case, anyone with a staff permission should be able
	to access the admin site
	"""
	user: User = request.user
	return user.is_active and (user.is_superuser or user.get_all_permissions())


# Register our new admin permission
admin.site.has_permission = has_admin_site_permission

# Register other models
admin.site.register(ProjectDiscipline)
admin.site.register(AccountType)
admin.site.register(ResourceCategory)
admin.site.register(Permission)
