from functools import lru_cache

from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils.text import capfirst
from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy

from wagtail.admin import messages
from wagtail.admin.panels import (
    ObjectList,
    extract_panel_definitions_from_model_class,
)
from wagtail.admin.views import generic
from wagtail.models import Site
from wagtail.permission_policies import ModelPermissionPolicy

from .forms import SiteSwitchForm
from .models import BaseGenericSetting, BaseSiteSetting
from .registry import registry


def get_model_from_url_params(app_name, model_name):
    """
    retrieve a content type from an app_name / model_name combo.
    Throw Http404 if not a valid setting type
    """
    model = registry.get_by_natural_key(app_name, model_name)
    if model is None:
        raise Http404
    return model


@lru_cache(maxsize=None)
def get_setting_edit_handler(model):
    if hasattr(model, "edit_handler"):
        edit_handler = model.edit_handler
    else:
        if issubclass(model, BaseSiteSetting):
            panels = extract_panel_definitions_from_model_class(model, ["site"])
        elif issubclass(model, BaseGenericSetting):
            panels = extract_panel_definitions_from_model_class(model)
        else:
            raise NotImplementedError

        edit_handler = ObjectList(panels)
    return edit_handler.bind_to_model(model)


def redirect_to_relevant_instance(request, app_name, model_name):
    model = get_model_from_url_params(app_name, model_name)

    if issubclass(model, BaseSiteSetting):
        # Redirect the user to the edit page for the current site
        # (or the current request does not correspond to a site, the first site in the list)
        site_request = Site.find_for_request(request)
        site = site_request or Site.objects.first()
        if not site:
            messages.error(
                request,
                _("This setting could not be opened because there is no site defined."),
            )
            return redirect("wagtailadmin_home")
        return redirect(
            "wagtailsettings:edit",
            app_name,
            model_name,
            site.pk,
        )
    elif issubclass(model, BaseGenericSetting):
        return redirect(
            "wagtailsettings:edit",
            app_name,
            model_name,
            model.load(request_or_site=request).id,
        )
    else:
        raise NotImplementedError


class EditView(generic.EditView):
    template_name = "wagtailsettings/edit.html"
    error_message = gettext_lazy("The setting could not be saved due to errors.")
    permission_required = "change"

    def setup(self, request, app_name, model_name, *args, **kwargs):
        self.app_name = app_name
        self.model_name = model_name
        self.model = get_model_from_url_params(app_name, model_name)
        self.permission_policy = ModelPermissionPolicy(self.model)
        self.pk = kwargs.get(self.pk_url_kwarg)
        super().setup(request, app_name, model_name, *args, **kwargs)

    def get_object(self, queryset=None):
        self.site = None
        if issubclass(self.model, BaseSiteSetting):
            self.site = get_object_or_404(Site, pk=self.pk)
            return self.model.for_site(self.site)
        else:
            return get_object_or_404(self.model, pk=self.pk)

    def get_form_class(self):
        return get_setting_edit_handler(self.model).get_form_class()

    def get_edit_url(self):
        return reverse(
            "wagtailsettings:edit",
            args=(self.app_name, self.model_name, self.pk),
        )

    def get_success_buttons(self):
        return None

    def get_page_subtitle(self):
        return capfirst(self.model._meta.verbose_name)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        site_switcher = None
        if self.site and Site.objects.count() > 1:
            site_switcher = SiteSwitchForm(self.site, self.model)
            media = context.get("media") + site_switcher.media

        form = self.get_form()

        edit_handler = get_setting_edit_handler(self.model).get_bound_panel(
            instance=self.object, request=self.request, form=form
        )

        media = form.media + edit_handler.media

        header_icon = registry._model_icons.get(self.model)

        context.update(
            {
                "edit_handler": edit_handler,
                "site_switcher": site_switcher,
                "media": media,
                "header_icon": header_icon,
            }
        )

        return context

    def get_success_url(self):
        return self.get_edit_url()

    def get_success_message(self):
        return capfirst(
            _("%(setting_type)s updated.")
            % {"setting_type": self.model._meta.verbose_name}
        )
