from http import HTTPStatus
import logging
from typing import Any, Iterable, TYPE_CHECKING, Union

from unpoly.views import UnpolyViewMixin, UnpolyCrispyFormViewMixin
from urllib.parse import urlencode
from uuid import UUID

from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ImproperlyConfigured
from django.db import InternalError
from django.db.models import Model, QuerySet, ProtectedError, RestrictedError
from django.forms import ModelForm
from django.http import (
    Http404,
    HttpResponse,
    HttpResponseRedirect,
)
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from django.urls import resolve, reverse, NoReverseMatch, Resolver404
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as tr

from console_base.breadcrumbs import BreadcrumbTrail
from console_base.typehints import LCHttpRequest, StrOrPromise
from vanilla import CreateView, DeleteView, DetailView, UpdateView

if TYPE_CHECKING:
    from ..models.models import BaseIntegerPKModel, BaseUUIDPKModel
    from unpoly.unpoly import Unpoly

    class TypeHintBaseMixin:
        action: str
        up: 'Unpoly'
        request: LCHttpRequest
        object: BaseUUIDPKModel

        @cached_property
        def referer_url_name(self) -> StrOrPromise:
            return ''

        @property
        def css_icon(self) -> str:
            return ''

    class CreateViewBaseMixin(TypeHintBaseMixin, CreateView):
        pass

    class UpdateViewBaseMixin(TypeHintBaseMixin, UpdateView):
        pass

    class DetailViewBaseMixin(TypeHintBaseMixin, DetailView):
        pass

    class DeleteViewBaseMixin(TypeHintBaseMixin, DeleteView):
        pass

else:
    CreateViewBaseMixin = DetailViewBaseMixin = DeleteViewBaseMixin = UpdateViewBaseMixin = object

logger = logging.getLogger(__name__)
SCRUBBED_KEYS = {
    'submit',
    'cancel',
    '_method',
    'navbar_list_url',
}
SCRUBBED_PREFIXES = (
    'data-',
    'csrf',
    'next',
)


# ----------------------------------------------------------------------
class LogFormErrorsBaseMixin(CreateViewBaseMixin):
    """
    Log form errors for easier debugging.
    Display hidden form field errors to the user.
    """

    def form_invalid(self, form: ModelForm) -> HttpResponse:
        unpoly_validation = self.up.is_unpoly()

        if not unpoly_validation:
            logger.info('"%s" has errors: %s', form.__class__.__name__, form.errors)

        self.invalid_form_submission = True
        context = self.get_context_data(
            form=form,
            hidden_errors=[f.errors for f in form.hidden_fields() if f.errors],
        )

        if unpoly_validation:
            return TemplateResponse(
                request=self.request,
                template=self.get_template_names(),
                context=context,
                status=422,
            )

        return self.render_to_response(context)


# ----------------------------------------------------------------------
class UrlPropertiesBaseMixin(DetailViewBaseMixin):
    @cached_property
    def referer(self):
        """
        Generate URL details from referer URL, such as:
        http://localhost:8000/categories/consolecategory/detail/8a1fac66-2e08-11e8-8fd7-4439c46d4bd4/
        """
        try:
            url = self.request.META.get('HTTP_REFERER', '').split('/')
            return resolve(f'/{"/".join(url[3:])}')
        except (IndexError, Resolver404):
            return ''

    @cached_property
    def referer_url_name(self) -> StrOrPromise:
        try:
            return self.referer.url_name or ''
        except AttributeError:
            return ''

    @cached_property
    def referer_app_name(self) -> str:
        try:
            return self.referer.app_name
        except AttributeError:
            return ''

    @cached_property
    def app_name(self) -> str:
        # on 404 pages when DEBUG=False, URL won't have resolved!
        try:
            return self.request.resolver_match.app_name
        except AttributeError:
            return '404_url_not_found'

    @cached_property
    def url_name(self) -> str:
        # on 404 pages when DEBUG=False, URL won't have resolved!
        try:
            return self.request.resolver_match.url_name
        except AttributeError:
            return '404_url_not_found'


# ----------------------------------------------------------------------
class ScrubbedGetParams(UpdateViewBaseMixin):
    # -----------------------------------------------------
    def scrubbed_get_params(self) -> dict[str, Any]:
        """
        Check for string values of null GET params and remove them
        """
        # query dict values are [list], so create regular python dict
        qp_dict = dict(self.request.GET.items())

        params = {}

        for k, v in qp_dict.items():
            if not v or v == 'None':
                continue
            if k in SCRUBBED_KEYS:
                continue
            if k.startswith(SCRUBBED_PREFIXES):
                continue
            params[k] = v.strip('?')
        return params

    # -----------------------------------------------------
    def get_success_url(self) -> str:
        url = self.request.GET.get('next', '') or super().get_success_url()
        return f'{url}?{urlencode(self.scrubbed_get_params())}'


# ----------------------------------------------------------------------
class CrumbTrail(DetailViewBaseMixin):
    """
    Only include functionality that should be present
    in ALL logged-in Page Views.
    """

    def name(self) -> StrOrPromise:
        """Override on subclasses to return something interesting"""
        return ''

    # -----------------------------------------------------
    def update_crumb_trail(self, request: LCHttpRequest) -> None:
        """
        After calculating dispatch, generate breadcrumbs.
        Have access to delete URLs here which is what we need!
        """
        BreadcrumbTrail(
            request=request,
            name=self.name(),
            icon=self.css_icon,
        ).trail()

    # -----------------------------------------------------
    def catch_navbar_search(
        self,
        request: LCHttpRequest,
        response: HttpResponse,
    ) -> HttpResponse:
        """
        If user made a search from the Navbar, catch the
        params and direct to the correct page if the
        current url_name is not the correct one.
        """
        if request.method != 'GET' or 'navbar_list_url' not in request.GET:
            return response

        record_search = 'record_search'

        # in production mode, 404 pages won't resolve
        try:
            url_name = request.resolver_match.url_name
        except (AttributeError, NoReverseMatch):
            return response

        navbar_list_url = request.GET.get('navbar_list_url', '')
        params = urlencode(request.GET)

        # User searching from a list view, so redirect results to record_search
        if not navbar_list_url and url_name != record_search:
            return HttpResponseRedirect(f"{reverse(f'frontend:{record_search}')}?{params}")

        # User searching for results on the current page
        if navbar_list_url.endswith(url_name):
            return response

        try:
            url = reverse(navbar_list_url)
        except NoReverseMatch as e:
            logger.exception(e)
            return response

        return HttpResponseRedirect(f'{url}?{params}')

    # -----------------------------------------------------
    def dispatch(self, request: LCHttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
        """
        After calculating dispatch, generate breadcrumbs.
        Have access to delete URLs here which is what we need!
        """
        resp = super().dispatch(request, *args, **kwargs)

        self.update_crumb_trail(request)

        return self.catch_navbar_search(request, resp)  # noqa


# ----------------------------------------------------------------------
class PrefillForm(ScrubbedGetParams):
    """
    Prefill form fields from url query params
    """

    # -----------------------------------------------------
    def get_form(self, data: Any = None, files: Any = None, **kwargs: Any) -> ModelForm:
        initial = kwargs.pop('initial', {})
        initial |= self.scrubbed_get_params()

        # initial values defined from URL / View should override other initial values
        if view_kwargs := self.kwargs:
            initial |= view_kwargs
        kwargs['initial'] = initial

        return super().get_form(data=data, files=files, **kwargs)


# ----------------------------------------------------------------------
class ToggleOrDeleteMixin(DeleteViewBaseMixin):
    def toggle_object(self) -> None:
        if self.object.is_active:
            self.action = 'de-activate'
        else:
            self.action = 'activate'
        self.object.toggle()

    def delete_object(self) -> None:
        """
        Handle object deletion in a separate method so we don't need
        to override the post method for custom deletion functionality.

        Override on subclasses to add extra functionality,
        such as check for constraints or add pghistory context
        """
        try:
            self.object.delete(mode=self.delete_mode)
        # Catch up-stream libraries that don't have console-based
        # QuerySet delete method with the "mode" kwarg
        except TypeError:
            self.object.delete()
        except ProtectedError as e:
            self.display_constraint_message(e.protected_objects)
        except RestrictedError as e:
            self.display_constraint_message(e.restricted_objects)
        except InternalError as e:
            self.add_exception_error_messages(error=e)

    def display_constraint_message(self, records: Iterable[Model]) -> None:
        """
        Display list of related records that prevent the object from being deleted.
        """
        self.constraint_check_failed = True
        msg = f'Unable to delete {self.object}'
        messages.error(self.request, f'{msg}. Records below require this record to be present.')
        for obj in records:
            href = obj.get_absolute_url()  # type: ignore[attr-defined]
            url = f'<a href="{href}" class="alert-link">{obj}</a>'
            messages.error(self.request, url, extra_tags='safe')

    def add_exception_error_messages(self, error: Exception) -> None:
        """
        Override on subclasses to extend error messages
        """
        msg = f'Unable to delete {self.object}'
        logger.exception('%s: %s', msg, error)
        messages.error(self.request, f'{msg}. Data constraints require record to be present.')

    def post(self, request: LCHttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:
        self.delete_mode: str = request.GET.get('mode', self.delete_mode)  # noqa
        self.object = self.get_object()
        self.row_pk = self.object.row_pk  # save in case we need it later for redirecting, etc.

        if request.resolver_match.url_name.endswith('toggle'):
            self.toggle_object()
        else:
            self.delete_object()
            if self.constraint_check_failed:
                return self.error_response(request)

        if self.up.is_unpoly():
            remove_selector = self.get_remove_selector()
            if remove_selector:
                response = HttpResponse(status=204)
                data = self.record_event_data(remove_id=remove_selector)  # noqa
                return self.up.accept_layer(response, data)

            if self.send_optimized_response():  # noqa
                return self.optimized_response()  # noqa

            return self.up.emit(
                HttpResponse(status=204, headers={'X-Up-Target': ':none'}),
                'redirect:location',
                {'redirect_to': self.get_success_url()},
            )

        return HttpResponseRedirect(self.get_success_url())

    def error_response(self, request: LCHttpRequest) -> HttpResponse:
        """
        Return object URL to end user, so that `messages` assigned
        from the `delete_object` can be displayed to user.
        """
        try:
            self.object = self.get_object()
            url = self.object.get_absolute_url()
        except AttributeError:
            url = request.get_full_path()

        if self.up.is_unpoly():
            return TemplateResponse(
                request,
                settings.MESSAGES_TEMPLATE,
                context={'title': tr('Record constraints must be resolved')},
                status=HTTPStatus.CONFLICT,
                headers={'X-Up-Target': '#content_panel'},
            )

        return HttpResponseRedirect(url)

    def get_remove_selector(self) -> str:
        """
        After deletion events, we may want to remove an element
        from the page rather than redirecting to a new page.

        Override this method on subclasses to extend logic on
        backend, determining when/which item to remove.
        """
        return self.request.GET.get('remove_id', '')


# ----------------------------------------------------------------------
class LCUnpolyViewMixin(UnpolyViewMixin):
    load_form_in_modal = True


# ----------------------------------------------------------------------
class NoCrudButtonsMixin:
    """
    Mixin class to turn off all crud buttons on views.
    """

    def show_create_btn(self) -> bool:
        return False

    def show_update_btn(self) -> bool:
        return False

    def show_delete_btn(self) -> bool:
        return False


class BaseModelObject:
    """
    Fake object with enough attributes to enable views to be
    processed. The values should _not_ be visible in the template!
    """

    def model_name(self) -> str:
        return 'fake'

    def pretty_model_name(self) -> str:
        return 'Fake'

    def pretty_model_name_plural(self) -> str:
        return 'Fakes'

    @property
    def is_synced(self) -> bool:
        return False


if TYPE_CHECKING:
    from ..models.models import BaseIntegerPKModel, BaseUUIDPKModel

    class TypeHintMixin:
        action: str
        up: 'Unpoly'
        request: LCHttpRequest
        object: BaseUUIDPKModel

        @cached_property
        def referer_url_name(self) -> StrOrPromise:
            return ''

        @property
        def css_icon(self) -> str:
            return ''

    class CreateViewMixin(TypeHintMixin, CreateView):
        pass

    class UpdateViewMixin(TypeHintMixin, UpdateView):
        pass

    class DetailViewMixin(TypeHintMixin, DetailView):
        pass

    class DeleteViewMixin(
        TypeHintMixin,
        UnpolyViewMixin,
        UrlPropertiesBaseMixin,
        ToggleOrDeleteMixin,
        DeleteView,
    ):
        pass

else:
    CreateViewMixin = DetailViewMixin = DeleteViewMixin = UpdateViewMixin = object


# ----------------------------------------------------------------------
class LCUnpolyCrispyFormViewMixin(LogFormErrorsBaseMixin, UnpolyCrispyFormViewMixin):
    load_form_in_modal = True

    def send_optimized_success_response(self) -> bool:
        """
        If there's a referer detail page, we usually want to send an
        optimized success response, usually in the form of a Table Row.
        Otherwise, we're on the record's own detail page, and then sending
        back the default DetailView response is what's desired.
        """
        return super().send_optimized_success_response() and bool(self.referer_detail_page)

    @property
    def referer_detail_page(self) -> StrOrPromise:
        """
        For records such as Patterns & Action Groups, etc., it's helpful to
        know which Detail Page the user is on when Creating/Updating records,
        so we can return the right response to the user.

        Mostly this is to enable the AccessPolicy Dashboard to display
        different messages to the user than they'd see on the normal Detail page.

        Also, to know whether "create" action was triggered from List View or
        Dashboard View, in which case we need to reload the whole page.
        """
        optimized_response = self.kwargs.get(settings.OPTIMIZED_SUCCESS_RESPONSE)
        dp = optimized_response or self.request.GET.get('detail_page')
        if dp:
            return dp

        try:
            # When URL prefix and referer prefix are the same, then we're on the
            # detail page of the same record in question. Not an interesting situation.
            referer_prefix = self.referer_url_name.split('_')[0]
            url_prefix = self.url_name.split('_')[0]  # noqa
            if referer_prefix != url_prefix:
                return self.referer_url_name
        except (AttributeError, IndexError):
            pass

        return ''


# ----------------------------------------------------------------------
class TenantView(DetailViewMixin):
    """
    Users may only retrieve records from the database if they're
    related to those records via Company or Policy relationship
    """

    def get_object_by_pk_or_cid(self) -> 'BaseUUIDPKModel':
        """
        Copy superclass get_object method and extend to lookup by Canonical ID.

        Only works on models where PK is UUID. Mostly a helper method for
        Block Page templates where we want to lookup builtin ACLs by CID.
        """
        queryset = self.get_queryset()
        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field

        try:
            lookup_value = self.kwargs[lookup_url_kwarg]
            lookup = {self.lookup_field: lookup_value}
        except KeyError:
            raise ImproperlyConfigured(
                f"Lookup field {lookup_url_kwarg!r} was not provided "
                f"in view kwargs to {self.__class__.__name__!r}"
            ) from None

        try:
            return get_object_or_404(queryset, **lookup)
        except self.model.MultipleObjectsReturned:
            logger.error(
                'Unexpected MultipleObjectsReturned for %s! Consider refining lookup params "%s"',
                self.model,
                lookup,
            )
            raise
        except Http404 as e:
            not_found = e

            if not isinstance(lookup_value, UUID):
                raise not_found

        try:
            return get_object_or_404(queryset, **{'cid': lookup_value})
        except self.model.MultipleObjectsReturned:
            logger.error(
                "Unexpected MultipleObjectsReturned for CID lookup on %s! "
                "Consider refining uniqueness constraints for %s",
                self.model,
                lookup_value,
            )
            raise

    def get_object(self) -> 'BaseUUIDPKModel':
        """
        Override to return a 404 if invalid value is passed as query param,
        and to lookup by default value first, and Canonical ID second.
        """
        try:
            # The has_permission method handles the check for login being required.
            # If login is not required, then has_permission will be True.
            if (
                not self.request.user.is_authenticated
                and settings.NO_LOGIN_REQUIRED not in self.get_permission_required()  # noqa
            ):
                return self.model.objects.none()
        except AttributeError:
            return self.model.objects.none()

        # cache to avoid hitting db if method
        # is called multiple  times per request
        if getattr(self, 'object', None):
            return self.object

        try:
            return self.get_object_by_pk_or_cid()
        except ValueError:
            raise Http404(f'Invalid query for {self.model}') from None

    def get_queryset(self) -> QuerySet:
        qs = super().get_queryset()

        try:
            return qs.tenant(user=self.request.user)
        except AttributeError:
            logger.info('%s is not a tenanted model; are you sure this is correct!?!', qs.model)
            return qs


# ----------------------------------------------------------------------
class ObjectURLs(CrumbTrail):
    """
    Helper functions to add variables
    for use in templates
    """

    # Should the query params of the current page be included
    # in the "Create" button URLs to pre-populate form state?
    encode_query_params_create_urls = False
    # URL to update record sequences from sortable table reordering
    sequence_url = ''
    action = ''

    def model_object(self) -> Union['BaseIntegerPKModel', type['BaseModelObject']]:
        """
        Get View Object if it has one
        Fallback to the View Model
        """
        if hasattr(self, 'object'):
            return self.object

        try:
            return self.model
        except AttributeError:
            return BaseModelObject

    def concord_event_tracked_model(self) -> bool:
        """
        Model is tracked by Concord event management
        framework (based on pghistory)
        """
        return hasattr(self.model_object(), 'pgh_event_model')

    def model_name(self) -> str:
        try:
            return self.model_object().model_name()  # type: ignore[call-arg]
        except TypeError:
            return self.model_object()().model_name()
        except AttributeError:
            return ''

    def crud_name(self) -> str:
        """
        Get name attribute from view on model, or view Object,
        for use on CRUD buttons. Override on subclasses to
        refine as needed.
        """
        try:
            return self.model_object().pretty_model_name()  # type: ignore[call-arg]
        except TypeError:
            return self.model_object()().pretty_model_name()
        except AttributeError:
            return ''

    def name(self) -> StrOrPromise:
        """Override on subclasses"""
        try:
            return f'{self.action.title()} {self.object.get_pretty_name or ""}'
        except AttributeError:
            return self.crud_name()

    def name_plural(self) -> StrOrPromise:
        """Get name attribute from view on model, or view Object"""
        try:
            return self.model_object().pretty_model_name_plural()  # type: ignore[call-arg]
        except TypeError:
            return self.model_object()().pretty_model_name_plural()
        except AttributeError:
            return ''

    def record_is_universal(self) -> bool:
        return self.kwargs.get(settings.UNIVERSAL_RECORD_KEY, False)

    def cloning_existing_record(self) -> bool:
        """
        If cloning an existing record, we need to relax universal record checks.
        """
        return self.request.GET.get(settings.CLONING_RECORD_KEY) or self.url_name.endswith('_clone')  # noqa

    # -----------------------------------------------------
    def get_form(self, data: Any = None, files: Any = None, **kwargs: Any) -> ModelForm:
        kwargs |= {
            'user': self.request.user,
            settings.CLONING_RECORD_KEY: self.cloning_existing_record(),
            settings.UNIVERSAL_RECORD_KEY: self.record_is_universal(),
        }

        return super().get_form(data=data, files=files, **kwargs)

    @cached_property
    def css_icon(self) -> str:
        return getattr(self.model_object(), 'css_icon', '')

    def get_create_url(self) -> str:
        """
        Get Create view from view method, but we can get
        List/Update/Delete views from the view.object.
        """
        try:
            url = self.model().get_create_url()
        except Exception:
            raise NotImplementedError(f'Define a "create" URL for {self}') from None

        if self.encode_query_params_create_urls:
            return f'{url}?{urlencode(self.request.GET)}'

        return url

    def get_detail_url_name(self) -> str:
        return self.model().get_detail_url_name()

    def get_list_url_name(self) -> str:
        return self.model().get_list_url_name()

    def get_list_url(self) -> str:
        return self.model().get_list_url()

    def get_toggle_url(self) -> str:
        return self.model().get_toggle_url()

    def show_create_btn(self) -> bool:
        """Show Create Button in breadcrumb bar"""
        if self.model_object() in (None, BaseModelObject):
            return False

        obj = self.object if hasattr(self, 'object') else self.model()
        if (has_perm := self.request.user.has_calculated_perm('add', obj)) is not None:
            return has_perm

        return self.request.user.is_company_user or self.request.user.is_accountability

    def show_update_btn(self) -> bool:
        """Show Update Button in breadcrumb bar"""
        if not (obj := getattr(self, 'object', None)):
            return False
        if not self.request.user.is_authenticated or obj.is_builtin:
            return False
        if obj.is_universal() and not self.request.user.is_superuser:
            return False
        if obj.policy_controlled and not self.request.user.is_accountability:
            return False
        if (has_perm := self.request.user.has_calculated_perm('change', obj)) is not None:
            return has_perm
        return self.request.user.is_company_user or self.request.user.is_accountability

    def show_pattern_update_btn(self) -> bool:
        """
        Separate button for patterns, such as Category patterns,
        or devices on a Company record.

        Sometimes the main record won't be updatable but pattern records will be.
        For example, Main Company won't be updatable but devices on that company are.
        """
        return self.show_update_btn()

    def show_record_activity_stream_btn(self) -> bool:
        """
        Activity Stream buttons are typically displayed only to users with specific
        permissions. Customize on subclasses as necessary. Keep in mind that any
        refinement of logic here needs to be supported by the View Permission!
        """
        if not self.concord_event_tracked_model():
            return False

        if not self.request.user.is_authenticated:
            return False

        try:
            return self.request.user.has_perm(settings.SHOW_ACTIVITY_STREAM_PERM)
        except AttributeError:
            logger.info('SHOW_ACTIVITY_STREAM_PERM setting is not defined')
            return False

    def show_record_activity_stream_deletes_btn(self) -> bool:
        """
        Should only be activated on list views.
        """
        return False

    def show_pattern_activity_stream_btn(self) -> bool:
        return self.show_record_activity_stream_btn()

    def show_clone_btn(self) -> bool:
        """Show Clone Button in breadcrumb bar"""
        if not (obj := getattr(self, 'object', None)):
            return False

        try:
            url = obj.get_clone_url()
            return bool(url) and url != '#'
        except AttributeError:
            return False

    def show_toggle_btn(self) -> bool:
        """Show Toggle Button in breadcrumb bar"""
        return False

    def show_delete_btn(self) -> bool:
        """Show Delete Button in breadcrumb bar"""
        if not (obj := getattr(self, 'object', None)):
            return False
        if not self.request.user.is_authenticated or obj.unremovable() or obj.is_preset():
            return False
        if obj.policy_controlled and not self.request.user.is_accountability:
            return False
        if obj.is_universal() and not self.request.user.is_superuser:
            return False
        if (has_perm := self.request.user.has_calculated_perm('delete', obj)) is not None:
            return has_perm
        return self.request.user.is_company_user or self.request.user.is_accountability

    def show_pattern_delete_btn(self) -> bool:
        """
        Separate button for patterns, such as Category patterns,
        or devices on a Company record.

        Sometimes the main record won't be deletable but pattern records will be.
        For example, Main Company won't be deletable but devices on that company are.
        """
        return self.show_delete_btn()

    # -----------------------------------------------------
    def show_context_menu_btn(self) -> bool:
        """Show context dropdown menu in the title panel on detail and list pages"""
        return False

    # -----------------------------------------------------
    def show_sync_menu_btn(self) -> bool:
        """Show sync dropdown menu in the title panel on detail pages"""
        return False

    # -----------------------------------------------------
    def get_context_data(self, **kwargs: Any) -> dict:
        if self.sequence_url:
            kwargs['sequence_url'] = reverse(self.sequence_url)

        return super().get_context_data(
            crud=self.action,
            show_clone_btn=self.show_clone_btn(),
            show_create_btn=self.show_create_btn(),
            show_delete_btn=self.show_delete_btn(),
            show_toggle_btn=self.show_toggle_btn(),
            show_update_btn=self.show_update_btn(),
            show_pattern_activity_stream_btn=self.show_pattern_activity_stream_btn(),
            show_record_activity_stream_btn=self.show_record_activity_stream_btn(),
            show_record_activity_stream_deletes_btn=self.show_record_activity_stream_deletes_btn(),
            show_pattern_update_btn=self.show_pattern_update_btn(),
            show_pattern_delete_btn=self.show_pattern_delete_btn(),
            show_context_menu_btn=self.show_context_menu_btn(),
            show_sync_menu_btn=self.show_sync_menu_btn(),
            url_name=self.url_name,  # noqa
            view_css_icon=self.css_icon,
            view_name=self.name,
            view_crud_name=self.crud_name(),
            view_model_name=self.model_name(),
            **kwargs,
        )


# ----------------------------------------------------------------------
class DeletePublicKeyMixin(DeleteViewMixin):
    """
    Mixin class to enable easy deletion of PublicKey(s)
    that may be related to a specific record.
    """

    def get_warning_title(self) -> StrOrPromise:
        if self.is_public_key_deletion():
            return tr('Are you sure you want to delete the Public Key(s) related to this record?')
        return super().get_warning_title()  # noqa

    def is_public_key_deletion(self) -> bool:
        """
        Check URL kwarg to see if this view is for deleting public keys.
        """
        if public_key_deletion := self.kwargs.get(settings.PUBLIC_KEY_DELETION, False):
            self.reload_detail_page = True
        return public_key_deletion

    def superuser_denied_perms(self):
        if self.is_human_staff_superuser() and self.is_public_key_deletion():  # noqa
            return False
        return super().superuser_denied_perms()  # noqa

    def get_public_key_object(self) -> 'BaseUUIDPKModel':
        """
        Get PublicKey object(s) for deletion
        """
        raise NotImplementedError('PublicKey lookup not configured')

    def get_public_key_deletion_success_msg(self) -> StrOrPromise:
        return tr('Public Keys deletion succeeded.')

    def get_public_key_deletion_not_found_msg(self) -> StrOrPromise:
        return tr('No Public Keys associated with this record.')

    def get_public_key_deletion_error_msg(self) -> StrOrPromise:
        return tr('An error occurred - unable to delete Public Keys.')

    def delete_object(self):
        if self.is_public_key_deletion():
            try:
                public_keys = self.get_public_key_object()
                if not public_keys:
                    messages.info(self.request, self.get_public_key_deletion_not_found_msg())
                    return

                result = public_keys.delete()
                records_deleted = result[0]
                if records_deleted:
                    messages.success(self.request, self.get_public_key_deletion_success_msg())
                else:
                    messages.info(self.request, self.get_public_key_deletion_not_found_msg())

            except Exception as e:
                logger.exception('Deleting Public Keys failed - %s', e)
                messages.error(self.request, self.get_public_key_deletion_error_msg())
        else:
            return super().delete_object()


__all__ = (
    'CrumbTrail',
    'DeletePublicKeyMixin',
    'LogFormErrorsBaseMixin',
    'LCUnpolyViewMixin',
    'LCUnpolyCrispyFormViewMixin',
    'ObjectURLs',
    'PrefillForm',
    'ScrubbedGetParams',
    'TenantView',
    'ToggleOrDeleteMixin',
    'NoCrudButtonsMixin',
)
