from __future__ import absolute_import, unicode_literals

import logging

from django.conf.urls import include, url
from django.db import transaction
from django.http import Http404
from django.shortcuts import redirect, render
from django.template.defaultfilters import pluralize
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from wagtail.admin import messages
from wagtail.admin.site_summary import PagesSummaryItem, SummaryItem
from wagtail.admin.views.pages import get_valid_next_url_from_request
from wagtail.admin.widgets import Button, ButtonWithDropdownFromHook
from wagtail.core import hooks
from wagtail.core.models import Page

from wagtail_personalisation import admin_urls, models, utils
from wagtail_personalisation.adapters import get_segment_adapter

logger = logging.getLogger(__name__)


@hooks.register('register_admin_urls')
def register_admin_urls():
    """Adds the administration urls for the personalisation apps."""
    return [
        url(r'^personalisation/', include(
            admin_urls, namespace='wagtail_personalisation')),
    ]


@hooks.register('before_serve_page')
def set_visit_count(page, request, serve_args, serve_kwargs):
    """Tests the provided rules to see if the request still belongs
    to a segment.

    :param page: The page being served
    :type page: wagtail.core.models.Page
    :param request: The http request
    :type request: django.http.HttpRequest

    """
    adapter = get_segment_adapter(request)
    adapter.add_page_visit(page)


@hooks.register('before_serve_page')
def segment_user(page, request, serve_args, serve_kwargs):
    """Apply a segment to a visitor before serving the page.

    :param page: The page being served
    :type page: wagtail.core.models.Page
    :param request: The http request
    :type request: django.http.HttpRequest

    """
    adapter = get_segment_adapter(request)
    adapter.refresh()

    forced_segment = request.GET.get('segment', None)
    if request.user.is_superuser and forced_segment is not None:
        segment = models.Segment.objects.filter(pk=forced_segment).first()
        if segment:
            adapter.set_segments([segment])


class UserbarSegmentedLinkItem:
    def __init__(self, segment):
        self.segment = segment

    def render(self, request):
        return f"""<div class="wagtail-userbar__item">
            <a href="{request.path}?segment={self.segment.pk}"
                class="wagtail-action">
                    Show as segment: {self.segment.name}</a></div>"""


@hooks.register('construct_wagtail_userbar')
def add_segment_link_items(request, items):
    for item in models.Segment.objects.enabled():
        items.append(UserbarSegmentedLinkItem(item))
    return items


@hooks.register('before_serve_page')
def serve_variant(page, request, serve_args, serve_kwargs):
    """Apply a segment to a visitor before serving the page.

    :param page: The page being served
    :type page: wagtail.core.models.Page
    :param request: The http request
    :type request: django.http.HttpRequest
    :returns: A variant if one is available for the visitor's segment,
              otherwise the original page
    :rtype: wagtail.core.models.Page

    """
    user_segments = []
    if not isinstance(page, models.PersonalisablePageMixin):
        return

    adapter = get_segment_adapter(request)
    user_segments = adapter.get_segments()

    metadata = page.personalisation_metadata

    # If page is not canonical, don't serve it.
    if not metadata.is_canonical:
        raise Http404

    if user_segments:
        # TODO: This is never more then one page? (fix query count)
        metadata = metadata.metadata_for_segments(user_segments)
        if metadata:
            variant = metadata.first().variant.specific
            return variant.serve(request, *serve_args, **serve_kwargs)


@hooks.register('construct_explorer_page_queryset')
def dont_show_variant(parent_page, pages, request):
    return utils.exclude_variants(pages)


@hooks.register('register_page_listing_buttons')
def page_listing_variant_buttons(page, page_perms, is_parent=False):
    """Adds page listing buttons to personalisable pages. Shows variants for
    the page (if any) and a 'Create a new variant' button.

    """
    if not isinstance(page, models.PersonalisablePageMixin):
        return

    metadata = page.personalisation_metadata
    if metadata.is_canonical:
        yield ButtonWithDropdownFromHook(
            _('Variants'),
            hook_name='register_page_listing_variant_buttons',
            page=page,
            page_perms=page_perms,
            is_parent=is_parent,
            attrs={'target': '_blank', 'title': _('Create or edit a variant')},
            priority=100)


@hooks.register('register_page_listing_variant_buttons')
def page_listing_more_buttons(page, page_perms, is_parent=False):
    """Adds a 'more' button to personalisable pages allowing users to quickly
    create a new variant for the selected segment.

    """
    if not isinstance(page, models.PersonalisablePageMixin):
        return

    metadata = page.personalisation_metadata

    for vm in metadata.variants_metadata:
        yield Button('%s variant' % (vm.segment.name),
                     reverse('wagtailadmin_pages:edit', args=[vm.variant_id]),
                     attrs={"title": _('Edit this variant')},
                     classes=("icon", "icon-fa-pencil"),
                     priority=0)

    for segment in metadata.get_unused_segments():
        yield Button('%s variant' % (segment.name),
                     reverse('segment:copy_page', args=[page.pk, segment.pk]),
                     attrs={"title": _('Create this variant')},
                     classes=("icon", "icon-fa-plus"),
                     priority=100)

    yield Button(_('Create a new segment'),
                 reverse('wagtail_personalisation_segment_modeladmin_create'),
                 attrs={"title": _('Create a new segment')},
                 classes=("icon", "icon-fa-snowflake-o"),
                 priority=200)


class CorrectedPagesSummaryItem(PagesSummaryItem):
    def get_context(self):
        # Perform the same check as Wagtail to get the correct count.
        # Only correct the count when a root page is available to the user.
        # The `PagesSummaryItem` will return a page count of 0 otherwise.
        # https://github.com/wagtail/wagtail/blob/5c9ff23e229acabad406c42c4e13cbaea32e6c15/wagtail/admin/site_summary.py#L38
        context = super().get_context()
        root_page = context.get('root_page', None)
        if root_page:
            pages = utils.exclude_variants(
                Page.objects.descendant_of(root_page, inclusive=True))
            page_count = pages.count()

            if root_page.is_root():
                page_count -= 1

            context['total_pages'] = page_count

        return context


@hooks.register('construct_homepage_summary_items')
def add_corrected_pages_summary_panel(request, items):
    """Replaces the Pages summary panel to hide variants."""
    for index, item in enumerate(items):
        if item.__class__ is PagesSummaryItem:
            items[index] = CorrectedPagesSummaryItem(request)


class SegmentSummaryPanel(SummaryItem):
    """The segment summary panel showing the total amount of segments on the
    site and allowing quick access to the Segment dashboard.

    """
    order = 2000

    def render(self):
        segment_count = models.Segment.objects.count()
        target_url = reverse('wagtail_personalisation_segment_modeladmin_index')
        title = _("Segments")
        return mark_safe("""
            <li class="icon icon-fa-snowflake-o">
                <a href="{}"><span>{}</span>{}</a>
            </li>""".format(target_url, segment_count, title))


class PersonalisedPagesSummaryPanel(PagesSummaryItem):
    order = 2100

    def render(self):
        page_count = models.PersonalisablePageMetadata.objects.filter(
            segment__isnull=True).count()
        title = _("Personalised Page")
        return mark_safe("""
            <li class="icon icon-fa-file-o">
                <span>{}</span>{}{}
            </li>""".format(page_count, title, pluralize(page_count)))


class VariantPagesSummaryPanel(PagesSummaryItem):
    order = 2200

    def render(self):
        page_count = models.PersonalisablePageMetadata.objects.filter(
            segment__isnull=False).count()
        title = _("Variant")
        return mark_safe("""
                <li class="icon icon-fa-files-o">
                    <span>{}</span>{}{}
                </li>""".format(page_count, title, pluralize(page_count)))


@hooks.register('construct_homepage_summary_items')
def add_personalisation_summary_panels(request, items):
    """Adds a summary panel to the Wagtail dashboard showing the total amount
    of segments on the site and allowing quick access to the Segment
    dashboard.

    """
    items.append(SegmentSummaryPanel(request))
    items.append(PersonalisedPagesSummaryPanel(request))
    items.append(VariantPagesSummaryPanel(request))


@hooks.register('before_delete_page')
def delete_related_variants(request, page):
    if not isinstance(page, models.PersonalisablePageMixin) \
            or not page.personalisation_metadata.is_canonical:
        return
    # Get a list of related personalisation metadata for all the related
    # variants.
    variants_metadata = (
        page.personalisation_metadata.variants_metadata
                                     .select_related('variant')
    )
    next_url = get_valid_next_url_from_request(request)

    if request.method == 'POST':
        parent_id = page.get_parent().id
        variants_metadata = variants_metadata.select_related('variant')
        with transaction.atomic():
            for metadata in variants_metadata.iterator():
                # Call delete() on objects to trigger any signals or hooks.
                metadata.variant.delete()
            # Delete the page's main variant and the page itself.
            page.personalisation_metadata.delete()
            page.delete()
        msg = _("Page '{0}' and its variants deleted.")
        messages.success(
            request,
            msg.format(page.get_admin_display_title())
        )

        for fn in hooks.get_hooks('after_delete_page'):
            result = fn(request, page)
            if hasattr(result, 'status_code'):
                return result

        if next_url:
            return redirect(next_url)
        return redirect('wagtailadmin_explore', parent_id)

    return render(
        request,
        'wagtailadmin/pages/wagtail_personalisation/confirm_delete.html', {
            'page': page,
            'descendant_count': page.get_descendant_count(),
            'next': next_url,
            'variants': Page.objects.filter(
                pk__in=variants_metadata.values_list('variant_id')
            )
        }
    )
