# coding=utf-8
import re

import django
from django.apps import apps
from django.conf import settings as django_settings
from django.conf.urls import url
from django.contrib.admin import AdminSite, ModelAdmin
from django.contrib.admin.sites import site as django_admin_site
from django.contrib.auth.decorators import permission_required, user_passes_test
from django.db.models import Model
from django.shortcuts import render
from django.utils.encoding import force_str
from django.utils.functional import cached_property
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from django.views.generic import View

from .. import compat
from ..environment import env

__all__ = ["ModelAdminView", "ViewTool", "AdminView", "register", "site"]


class ExtendedAdminSite(AdminSite):

    default_settings = {
        "INHERIT_REGISTERED_MODELS": env.get_bool(
            "DJANGO_ADMIN_INHERIT_REGISTERED_MODELS", True
        ),
        "SITE_TITLE": env.get("DJANGO_ADMIN_SITE_TITLE", AdminSite.site_title),
        "SITE_HEADER": env.get("DJANGO_ADMIN_SITE_HEADER", "admin"),
        "SITE_VERSION": env.get("DJANGO_ADMIN_SITE_VERSION", django.__version__),
        "INDEX_TITLE": env.get("DJANGO_ADMIN_INDEX_TITLE", AdminSite.index_title),
        "PRIMARY_COLOR": env.get("DJANGO_ADMIN_PRIMARY_COLOR", "#34A77B"),
        "SECONDARY_COLOR": env.get("DJANGO_ADMIN_SECONDARY_COLOR", "#20AA76"),
        "LOGO": env.get("DJANGO_ADMIN_LOGO", "admin/bananas/img/django.svg"),
        "LOGO_ALIGN": env.get("DJANGO_ADMIN_LOGO_ALIGN", "middle"),
        "LOGO_STYLE": env.get("DJANGO_ADMIN_LOGO_STYLE"),
    }

    def __init__(self, name="admin"):
        super().__init__(name=name)
        self.settings = dict(self.default_settings)
        self.settings.update(getattr(django_settings, "ADMIN", {}))

        self.site_title = self.settings["SITE_TITLE"]
        self.site_header = self.settings["SITE_HEADER"]
        self.index_title = self.settings["INDEX_TITLE"]

    def each_context(self, request):
        context = super().each_context(request)
        context.update(settings=self.settings)
        return context

    @property
    def urls(self):
        if self.settings["INHERIT_REGISTERED_MODELS"]:
            for model, admin in list(django_admin_site._registry.items()):
                # django_admin_site.unregister(model)
                self._registry[model] = admin.__class__(model, self)
        return self.get_urls(), "admin", self.name


class ModelAdminView(ModelAdmin):
    @cached_property
    def access_permission(self):
        meta = self.model._meta
        return "{app_label}.{codename}".format(
            app_label=meta.app_label,
            codename=meta.permissions[0][0],  # First perm codename
        )

    def get_urls(self):
        app_label = self.model._meta.app_label
        View = self.model.View
        info = app_label, View.label
        urlpatterns = compat.urlpatterns(
            url(
                r"^$",
                self.admin_view(View.as_view(admin=self)),
                name="{}_{}".format(*info),
            ),
            # We add the same url here with _changelist to make sure the
            # admin app index reverse urls to correct view.
            url(
                r"^$",
                self.admin_view(View.as_view(admin=self)),
                name="{}_{}_changelist".format(*info),
            ),
        )
        extra_urls = self.model.View(admin=self).get_urls()
        if extra_urls:
            urlpatterns += extra_urls
        return urlpatterns

    def admin_view(self, view, perm=None):
        if perm is not None:
            perm = self.get_permission(perm)
        else:
            perm = self.access_permission

        admin_login_url = compat.reverse_lazy("admin:login")
        view = user_passes_test(
            lambda u: u.is_active and u.is_staff, login_url=admin_login_url
        )(view)
        view = permission_required(perm, login_url=admin_login_url)(view)
        return view

    def get_permission(self, perm):
        if "." not in perm:
            perm = "{}.{}".format(self.model._meta.app_label, perm)
        return perm

    def has_module_permission(self, request):
        return request.user.has_perm(self.access_permission)

    def has_change_permission(self, request, obj=None):
        return request.user.has_perm(self.access_permission)

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

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

    def get_context(self, request, **extra):
        opts = self.model._meta
        context = self.admin_site.each_context(request)
        context.update(
            {
                "app_label": opts.app_label,
                "model_name": force_str(opts.verbose_name_plural),
                "title": force_str(opts.verbose_name_plural),
                "cl": {"opts": opts},  # change_list.html requirement
                "opts": opts,  # change_form.html requirement
                "media": self.media,
            }
        )
        context.update(extra or {})
        return context


def register(view=None, *, admin_site=None, admin_class=ModelAdminView):
    """
    Register a generic class based view wrapped with ModelAdmin and fake model

    :param view: The AdminView to register.
    :param admin_site: The AdminSite to register the view on.
        Defaults to bananas.admin.ExtendedAdminSite.
    :param admin_class: The ModelAdmin class to use for eg. permissions.
        Defaults to bananas.admin.ModelAdminView.

    Example:

    @register  # Or with args @register(admin_class=MyModelAdminSubclass)
    class MyAdminView(bananas.admin.AdminView):
        def get(self, request):
            return self.render('template.html', {})

    # Also possible:
    register(MyAdminView, admin_class=MyModelAdminSublass)

    """
    if not admin_site:
        admin_site = site

    def wrapped(inner_view):
        module = inner_view.__module__
        app_label = re.search(r"\.?(\w+)\.admin", module).group(1)
        app_config = apps.get_app_config(app_label)

        label = getattr(inner_view, "label", None)
        if not label:
            label = re.sub("(Admin)|(View)", "", inner_view.__name__).lower()
        inner_view.label = label

        model_name = label.capitalize()
        verbose_name = getattr(inner_view, "verbose_name", model_name)
        inner_view.verbose_name = verbose_name

        access_perm_codename = "can_access_" + model_name.lower()
        access_perm_name = _("Can access {verbose_name}").format(
            verbose_name=verbose_name
        )
        # The first permission here is expected to be
        # the general access permission.
        permissions = tuple(
            [(access_perm_codename, access_perm_name)]
            + list(getattr(inner_view, "permissions", []))
        )

        model = type(
            model_name,
            (Model,),
            {
                "__module__": module + ".__models__",  # Fake
                "View": inner_view,
                "app_config": app_config,
                "Meta": type(
                    "Meta",
                    (object,),
                    dict(
                        managed=False,
                        abstract=True,
                        app_label=app_config.label,
                        verbose_name=verbose_name,
                        verbose_name_plural=verbose_name,
                        permissions=permissions,
                    ),
                ),
            },
        )

        admin_site._registry[model] = admin_class(model, admin_site)
        return inner_view

    if view is None:  # Used as a decorator
        return wrapped

    return wrapped(view)


class ViewTool:
    def __init__(self, text, link, perm=None, **attrs):
        self.text = text
        self._link = link
        self.perm = perm

        html_class = attrs.pop("html_class", None)
        if html_class is not None:
            attrs.setdefault("class", html_class)
        self._attrs = attrs

    @property
    def attrs(self):
        return mark_safe(" ".join("{}={}".format(k, v) for k, v in self._attrs.items()))

    @property
    def link(self):
        if "/" not in self._link:
            return compat.reverse(self._link)
        return self._link


class AdminView(View):
    admin = None  # type: ModelAdminView
    tools = None
    action = None

    def dispatch(self, request, *args, **kwargs):
        # Try to fetch set action first.
        # This should be the view name for custom views
        action = self.action
        if action is None:
            return super().dispatch(request, *args, **kwargs)

        handler = getattr(self, action)
        return handler(request, *args, **kwargs)

    def get_urls(self):
        """ Should return a list of urls
        Views should be wrapped in `self.admin_view` if the view isn't
        supposed to be accessible for non admin users.
        Omitting this can cause threading issues.

        Example:

            return [
                url(r'^custom/$',
                    self.admin_view(self.custom_view))
            ]
        """
        return None

    def get_tools(self):
        # Override point, self.request is available.
        return self.tools

    def get_view_tools(self):
        tools = []
        all_tools = self.get_tools()
        if all_tools:
            for tool in all_tools:
                if isinstance(tool, (list, tuple)):
                    perm = None
                    if len(tool) == 3:
                        tool, perm = tool[:-1], tool[-1]
                    text, link = tool
                    tool = ViewTool(text, link, perm=perm)
                else:
                    # Assume ViewTool
                    perm = tool.perm
                if perm and not self.has_permission(perm):
                    continue

                tools.append(tool)

        return tools

    def admin_view(self, view, perm=None, **initkwargs):
        view = self.__class__.as_view(
            action=view.__name__, admin=self.admin, **initkwargs
        )
        return self.admin.admin_view(view, perm=perm)

    def get_permission(self, perm):
        return self.admin.get_permission(perm)

    def has_permission(self, perm):
        perm = self.get_permission(perm)
        return self.request.user.has_perm(perm)

    def has_access(self):
        return self.has_permission(self.admin.access_permission)

    def get_context(self, **extra):
        return self.admin.get_context(
            self.request, view_tools=self.get_view_tools(), **extra
        )

    def render(self, template, context=None):
        extra = context or {}
        return render(self.request, template, self.get_context(**extra))


site = ExtendedAdminSite()