import datetime
from collections import OrderedDict

from django.contrib.admin.utils import quote
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils.translation import gettext, gettext_lazy, ngettext
from django.views.generic import TemplateView
from django_filters import DateFromToRangeFilter

from wagtail.admin import messages
from wagtail.admin.filters import DateRangePickerWidget, WagtailFilterSet
from wagtail.admin.ui.tables import Column, TitleColumn
from wagtail.admin.views import generic
from wagtail.admin.views.generic.base import BaseListingView
from wagtail.admin.views.mixins import SpreadsheetExportMixin
from wagtail.contrib.forms.utils import get_forms_for_user
from wagtail.models import Page


def get_submissions_list_view(request, *args, **kwargs):
    """Call the form page's list submissions view class"""
    page_id = kwargs.get("page_id")
    form_page = get_object_or_404(Page, id=page_id).specific
    return form_page.serve_submissions_list_view(request, *args, **kwargs)


class ContentTypeColumn(Column):
    edit_url_name = "wagtailadmin_pages:edit"
    cell_template_name = "wagtailforms/content_type_column.html"

    def get_url(self, instance):
        return reverse(self.edit_url_name, args=(quote(instance.pk),))

    def get_cell_context_data(self, instance, parent_context):
        context = super().get_cell_context_data(instance, parent_context)
        context["url"] = self.get_url(instance)
        return context


class FormPagesListView(generic.IndexView):
    """Lists the available form pages for the current user"""

    template_name = "wagtailforms/index.html"
    results_template_name = "wagtailforms/index_results.html"
    context_object_name = "form_pages"
    paginate_by = 20
    page_kwarg = "p"
    index_url_name = "wagtailforms:index"
    index_results_url_name = "wagtailforms:index_results"
    page_title = gettext_lazy("Forms")
    header_icon = "form"
    _show_breadcrumbs = True
    columns = [
        TitleColumn(
            "title",
            classname="title",
            label=gettext_lazy("Title"),
            width="50%",
            url_name="wagtailforms:list_submissions",
            sort_key="title",
        ),
        ContentTypeColumn(
            "content_type",
            label=gettext_lazy("Origin"),
            width="50%",
            sort_key="content_type",
        ),
    ]
    model = Page
    is_searchable = False

    def get_breadcrumbs_items(self):
        return self.breadcrumbs_items + [
            {"url": "", "label": self.page_title, "sublabel": gettext("Pages")},
        ]

    def get_base_queryset(self):
        """Return the queryset of form pages for this view"""
        return get_forms_for_user(self.request.user).select_related("content_type")


class DeleteSubmissionsView(TemplateView):
    """Delete the selected submissions"""

    template_name = "wagtailforms/confirm_delete.html"
    page = None
    submissions = None
    success_url = "wagtailforms:list_submissions"

    def get_queryset(self):
        """Returns a queryset for the selected submissions"""
        submission_ids = self.request.GET.getlist("selected-submissions")
        submission_class = self.page.get_submission_class()
        return submission_class._default_manager.filter(id__in=submission_ids)

    def handle_delete(self, submissions):
        """Deletes the given queryset"""
        count = submissions.count()
        submissions.delete()
        messages.success(
            self.request,
            ngettext(
                "One submission has been deleted.",
                "%(count)d submissions have been deleted.",
                count,
            )
            % {"count": count},
        )

    def get_success_url(self):
        """Returns the success URL to redirect to after a successful deletion"""
        return self.success_url

    def dispatch(self, request, *args, **kwargs):
        """Check permissions, set the page and submissions, handle delete"""
        page_id = kwargs.get("page_id")

        if not get_forms_for_user(self.request.user).filter(id=page_id).exists():
            raise PermissionDenied

        self.page = get_object_or_404(Page, id=page_id).specific

        self.submissions = self.get_queryset()

        if self.request.method == "POST":
            self.handle_delete(self.submissions)
            return redirect(self.get_success_url(), page_id)

        return super().dispatch(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        """Get the context for this view"""
        context = super().get_context_data(**kwargs)

        context.update(
            {
                "page": self.page,
                "submissions": self.submissions,
            }
        )

        return context


class SubmissionsListFilterSet(WagtailFilterSet):
    date = DateFromToRangeFilter(
        label=gettext_lazy("Submission date"),
        field_name="submit_time",
        widget=DateRangePickerWidget,
    )


class SubmissionsListView(SpreadsheetExportMixin, BaseListingView):
    """Lists submissions for the provided form page"""

    template_name = "wagtailforms/submissions_index.html"
    results_template_name = "wagtailforms/list_submissions.html"
    context_object_name = "submissions"
    form_page = None
    default_ordering = ("-submit_time",)
    ordering_csv = ("submit_time",)  # keep legacy CSV ordering
    orderable_fields = (
        "id",
        "submit_time",
    )  # used to validate ordering in URL
    page_title = gettext_lazy("Form data")
    header_icon = "form"
    paginate_by = 20
    filterset_class = SubmissionsListFilterSet
    forms_index_url_name = "wagtailforms:index"
    index_url_name = "wagtailforms:list_submissions"
    index_results_url_name = "wagtailforms:list_submissions_results"
    _show_breadcrumbs = True
    show_export_buttons = True

    def dispatch(self, request, *args, **kwargs):
        """Check permissions and set the form page"""

        self.form_page = kwargs.get("form_page")

        if not get_forms_for_user(request.user).filter(pk=self.form_page.id).exists():
            raise PermissionDenied

        if self.is_export:
            data_fields = self.form_page.get_data_fields()
            # Set the export fields and the headings for spreadsheet export
            self.list_export = [field for field, label in data_fields]
            self.export_headings = dict(data_fields)

        return super().dispatch(request, *args, **kwargs)

    def get_filterset_kwargs(self):
        kwargs = super().get_filterset_kwargs()
        kwargs["queryset"] = self.get_base_queryset()
        return kwargs

    def get_base_queryset(self):
        """Return queryset of form submissions"""
        submission_class = self.form_page.get_submission_class()
        queryset = submission_class._default_manager.filter(page=self.form_page)
        return queryset

    def get_validated_ordering(self):
        """Return a dict of field names with ordering labels if ordering is valid"""
        orderable_fields = self.orderable_fields or ()
        ordering = {}
        if self.is_export:
            #  Revert to CSV order_by submit_time ascending for backwards compatibility
            default_ordering = self.ordering_csv or ()
        else:
            default_ordering = self.default_ordering or ()
        if isinstance(default_ordering, str):
            default_ordering = (default_ordering,)
        ordering_strs = self.request.GET.getlist("order_by") or list(default_ordering)
        for order in ordering_strs:
            try:
                _, prefix, field_name = order.rpartition("-")
                if field_name in orderable_fields:
                    ordering[field_name] = (
                        prefix,
                        "descending" if prefix == "-" else "ascending",
                    )
            except (IndexError, ValueError):
                continue  # invalid ordering specified, skip it
        return ordering

    def get_ordering(self):
        """Return the field or fields to use for ordering the queryset"""
        ordering = self.get_validated_ordering()
        return [values[0] + name for name, values in ordering.items()]

    def get_filename(self):
        """Returns the base filename for the generated spreadsheet data file"""
        return "{}-export-{}".format(
            self.form_page.slug, datetime.datetime.today().strftime("%Y-%m-%d")
        )

    def render_to_response(self, context, **response_kwargs):
        if self.is_export:
            return self.as_spreadsheet(
                context["submissions"], self.request.GET.get("export")
            )
        return super().render_to_response(context, **response_kwargs)

    def to_row_dict(self, item):
        """Orders the submission dictionary for spreadsheet writing"""
        row_dict = OrderedDict(
            (field, item.get_data().get(field)) for field in self.list_export
        )
        return row_dict

    def get_index_url(self):
        return reverse(self.index_url_name, args=(self.form_page.id,))

    def get_index_results_url(self):
        return reverse(self.index_results_url_name, args=(self.form_page.id,))

    def get_page_subtitle(self):
        return self.form_page.get_admin_display_title()

    def get_breadcrumbs_items(self):
        return self.breadcrumbs_items + [
            {
                "url": reverse(self.forms_index_url_name),
                "label": gettext("Forms"),
            },
            {
                "url": "",
                "label": self.get_page_title(),
                "sublabel": self.get_page_subtitle(),
            },
        ]

    def get_context_data(self, **kwargs):
        """Return context for view"""
        context = super().get_context_data(**kwargs)
        submissions = context[self.context_object_name]
        data_fields = self.form_page.get_data_fields()
        data_rows = []
        context["submissions"] = submissions
        if not self.is_export:
            # Build data_rows as list of dicts containing model_id and fields
            for submission in submissions:
                form_data = submission.get_data()
                data_row = []
                for name, label in data_fields:
                    val = form_data.get(name)
                    if isinstance(val, list):
                        val = ", ".join(val)
                    data_row.append(val)
                data_rows.append({"model_id": submission.id, "fields": data_row})
            # Build data_headings as list of dicts containing model_id and fields
            ordering_by_field = self.get_validated_ordering()
            orderable_fields = self.orderable_fields
            data_headings = []
            for name, label in data_fields:
                order_label = None
                if name in orderable_fields:
                    order = ordering_by_field.get(name)
                    if order:
                        order_label = order[1]  # 'ascending' or 'descending'
                    else:
                        order_label = "orderable"  # not ordered yet but can be
                data_headings.append(
                    {
                        "name": name,
                        "label": label,
                        "order": order_label,
                    }
                )

            context.update(
                {
                    "form_page": self.form_page,
                    "data_headings": data_headings,
                    "data_rows": data_rows,
                }
            )

        return context
