from pydoc import classname
from typing import Any
from django import forms
from django.db import models
from django.utils import timezone
from django.urls import path, reverse
from django.views.generic import TemplateView
from django.utils.translation import gettext_lazy as _, gettext_lazy
from django.contrib.auth.models import Permission
from django.http import HttpRequest, HttpResponse

from wagtail.snippets.models import register_snippet
from wagtail.snippets.views.snippets import SnippetViewSet, IndexView
from wagtail.admin.views.generic import WagtailAdminTemplateMixin
from wagtail.admin.widgets.button import Button, ButtonWithDropdown, HeaderButton
from wagtail import hooks

from django_filters.filterset import FilterSet
from django_filters import filters

from .checks import FilterChoices, FilterMethodChoices
from .models import FilteredRequest, FilterSettings, Filter, FilterActionChoices
from .options import RequestFilters

class LogIndexView(IndexView):
    def get_paginate_by(self, queryset):
        return 50
    
    def get_header_buttons(self):
        buttons = super().get_header_buttons()
        if self.request.user.has_perms([
                f"{FilteredRequest._meta.app_label}.see_chart",
            ]):
            buttons.insert(0, HeaderButton(
                _('Analyse'),
                icon_name='history',
                url=reverse('filter_chart_view'),
                priority=0,
            ))
        return buttons
    
    def get_breadcrumbs_items(self):
        breadcrumbs = super().get_breadcrumbs_items()
        if self.request.user.has_perms([
                    f"{FilterSettings._meta.app_label}.change_{FilterSettings._meta.model_name}",
                ]):
            return [
                breadcrumbs[0],
                URL("wagtailsettings:edit", _("Settings"), {"app_name": FilterSettings._meta.app_label, "model_name": FilterSettings._meta.model_name}),
                breadcrumbs[-1],
            ]
        
        return breadcrumbs

class FilteredRequestViewSet(SnippetViewSet):
    model = FilteredRequest
    copy_view_enabled = False

    icon = 'clipboard-list'
    menu_order = 1500
    menu_name = "request_filters_log"
    menu_label = _("Request Filters Log")
    add_to_admin_menu = False
    add_to_settings_menu = False
    url_prefix = 'request_filters/log'
    admin_url_namespace = 'request_filters_log'

    index_view_class = LogIndexView

    list_display = (
        'get_list_title',
        'get_list_description',
        'get_match_performed',
        'created_at',
    )
    
register_snippet(FilteredRequest, FilteredRequestViewSet)


class URL(object):
    def __init__(self, url: str, label: str, reverse_kwargs: dict = None):
        if (reverse_kwargs is not None) and (reverse_kwargs is not True):
            url = reverse(url, kwargs=reverse_kwargs)
        elif reverse_kwargs is True:
            url = reverse(url)

        self.url = url
        self.label = label

    def keys(self):
        return ['url', 'label']

    def __getitem__(self, key):
        return getattr(self, key)


weekdays = {
    1: gettext_lazy("Monday"),
    2: gettext_lazy("Tuesday"),
    3: gettext_lazy("Wednesday"),
    4: gettext_lazy("Thursday"),
    5: gettext_lazy("Friday"),
    6: gettext_lazy("Saturday"),
    7: gettext_lazy("Sunday"),
}

months = {
    1: gettext_lazy("January"),
    2: gettext_lazy("February"),
    3: gettext_lazy("March"),
    4: gettext_lazy("April"),
    5: gettext_lazy("May"),
    6: gettext_lazy("June"),
    7: gettext_lazy("July"),
    8: gettext_lazy("August"),
    9: gettext_lazy("September"),
    10: gettext_lazy("October"),
    11: gettext_lazy("November"),
    12: gettext_lazy("December"),
}


def day_to_weekday(point):
    return weekdays[point.isoweekday()]


def integer_to_month(point):
    return months[point.month]


def _hour_data(annotations, qs, from_date: timezone.datetime, to_date: timezone.datetime):
    qs = qs.values(date=models.functions.TruncHour("created_at"))\
        .annotate(**annotations)\
        .values_list('date', *annotations.keys())\
        .order_by('date')
    
    hour_start = from_date.replace(minute=0, second=0, microsecond=0)
    hour_end = to_date.replace(minute=0, second=0, microsecond=0)

    labels = []
    while hour_start <= hour_end:
        labels.append(hour_start)
        hour_start = hour_start + timezone.timedelta(hours=1)

    return labels, qs, lambda point: point.strftime("%H:%M")


def _week_data(annotations, qs, from_date: timezone.datetime, to_date: timezone.datetime):
    qs = qs.values(date=models.functions.TruncDate("created_at"))\
        .annotate(**annotations)\
        .values_list('date', *annotations.keys())\
        .order_by('date')

    from_date = from_date.date()
    to_date = to_date.date()

    labels = []
    while from_date <= to_date:
        labels.append(from_date)
        from_date = from_date + timezone.timedelta(days=1)

    return labels, qs, day_to_weekday


def _date_data(annotations, qs, from_date: timezone.datetime, to_date: timezone.datetime):
    qs = qs.values(date=models.functions.TruncDate("created_at"))\
        .annotate(**annotations)\
        .values_list('date', *annotations.keys())\
        .order_by('date')
    
    from_date = from_date.date()
    to_date = to_date.date()

    labels = []
    while from_date <= to_date:
        labels.append(from_date)
        from_date = from_date + timezone.timedelta(days=1)

    return labels, qs, lambda point: point.strftime("%d-%m-%Y")


def _month_data(annotations, qs, from_date: timezone.datetime, to_date: timezone.datetime):
    qs = qs.annotate(date=models.functions.TruncMonth("created_at", output_field=models.DateField()))\
                       .values('date')\
                       .annotate(**annotations)\
                       .values_list('date', *annotations.keys())\
                       .order_by('date')

    start_date = from_date.date().replace(day=1)
    end_date = to_date.date().replace(day=1)

    labels = []
    while start_date <= end_date:
        labels.append(start_date)
        if start_date.month == 12:
            start_date = start_date.replace(year=start_date.year + 1, month=1)
        else:
            start_date = start_date.replace(month=start_date.month + 1)
        
    return labels, qs, lambda point: point.strftime("%m-%Y")

class RequestFiltersModelMultipleChoceField(filters.ModelMultipleChoiceField):
    widget = forms.CheckboxSelectMultiple

class RequestFilterModelMultipleChoiceFilter(filters.ModelMultipleChoiceFilter):
    field_class = RequestFiltersModelMultipleChoceField

class FilteredRequestFilterSet(FilterSet):
    method = filters.ChoiceFilter(
        choices=FilterMethodChoices.choices,
        method='filter_method',
        label=_('Method'),
    )

    filter = filters.ChoiceFilter(
        choices=FilterChoices.choices,
        method='filter_filter',
        label=_('Filter Type'),
    )

    chart_type = filters.ChoiceFilter(
        choices=[
            ('line', _('Line')),
            ('bar', _('Bar')),
        ],
        label=_('Chart Type'),
        empty_label=None,
        initial='line',
        method='filter_chart_type',
    )

    from_date = filters.DateTimeFilter(
        field_name='created_at',
        label=_('From Date'),
        method='filter_from_date',
    )

    to_date = filters.DateTimeFilter(
        field_name='created_at',
        label=_('To Date'),
        method='filter_to_date',
    )

    filters = RequestFilterModelMultipleChoiceFilter(
        queryset=Filter.objects.all(),
        method='filter_filters',
        label=_('Filters'),
    )

    def __init__(self, *args, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        self.form.fields['chart_type'].initial = 'line'

    class Meta:
        model = FilteredRequest
        fields = [
            'method',
            'filter',
            'chart_type',
            'filters',
        ]

    def filter_method(self, queryset, name, value):
        if not value:
            return queryset
        return queryset.filter(_filter__method=value)
    
    def filter_filter(self, queryset, name, value):
        if not value:
            return queryset
        return queryset.filter(_filter__filter_type=value)
        
    def filter_chart_type(self, queryset, name, value):
        return queryset
    
    def filter_to_date(self, queryset, name, value):
        return queryset
    
    def filter_from_date(self, queryset, name, value):
        return queryset

    def filter_filters(self, queryset, name, value):
        if not value:
            return queryset
        
        values = [v.pk for v in value]
        q = models.Q(_filter__pk=values[0])
        for v in values[1:]:
            q |= models.Q(_filter__pk=v)
        return queryset.filter(q)

class FilteredRequestChartView(WagtailAdminTemplateMixin, TemplateView):
    template_name = 'request_filters/chart_view.html'
    page_title = _('Filters Chart')
    page_subtitle = _('Analyse your filters')
    header_icon = 'clipboard-list'
    _show_breadcrumbs = True
    days = {
        "day": 1,
        "week": 7,
        "month": 30,
        "year": 365,
    }
    filters = {
        "day": _hour_data,
        "week": _week_data,
        "month": _date_data,
        "year": _month_data,
    }

    @property
    def header_buttons(self):
        filters = ["day", "week", "month", "year"]
        buttons = []

        query = self.request.GET.copy()
        query.pop('query_by', None)
        query.pop('from_date', None)

        for i, filter in enumerate(filters):
            # data = self.request.GET.get('filter', None)
            # if filter == data:
            #     continue

            url =  f"{reverse('filter_chart_view')}?query_by={filter}"
            if query:
                url += f"&{query.urlencode()}"

            buttons.append(
                Button(
                    _(filter.capitalize()),
                    url=url,
                    priority=i,
                )
            )

        ret_buttons = [
            ButtonWithDropdown(
                _('Filter'),
                # icon_name='filter',
                buttons=buttons,
                priority=0,
            )
        ]

        if self.request.user.has_perms([
                f"{FilteredRequest._meta.app_label}.view_{FilteredRequest._meta.model_name}"
            ]):
            ret_buttons.append(
                HeaderButton(
                    _('Logs'),
                    url=reverse('request_filters_log:list'),
                    icon_name='clipboard-list',
                    priority=10,
                )
            )

        return ret_buttons


    def get_breadcrumbs_items(self):

        if self.request.user.has_perms([
                    f"{FilterSettings._meta.app_label}.change_{FilterSettings._meta.model_name}",
                ]):

            return [
                URL("wagtailsettings:edit", _("Settings"), {"app_name": FilterSettings._meta.app_label, "model_name": FilterSettings._meta.model_name}),
                URL("filter_chart_view", _("Analyse your Filters"), True),
            ]
        
        return [
            URL("filter_chart_view", _("Analyse your Filters"), True),
        ]

    def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse:

        annotations = {}
        for action, __ in FilterActionChoices.choices:
            annotations[f'count_{action.lower()}'] = models.Count(
                '_filter',
                filter=models.Q(
                    _filter__action=action
                ),
            )

        filter = request.GET.get('query_by', 'day')
        dj_filter = FilteredRequestFilterSet(request.GET, queryset=FilteredRequest.objects.all(), request=request)
        qs = dj_filter.qs

        to_date = getattr(dj_filter.form, "cleaned_data", {}).get("to_date")
        from_date = getattr(dj_filter.form, "cleaned_data", {}).get("from_date")

        if from_date and not to_date:
            to_date = timezone.now()

        if from_date and to_date:
            delta_from_to = to_date - from_date
            if delta_from_to.days > 93:
                delta = "year"
            elif delta_from_to.days >= 30:
                delta = "month"
            elif delta_from_to.days > 7:
                delta = "month"
            elif delta_from_to.days > 1:
                delta = "week"
            elif delta_from_to.days <= 1:
                delta = "day"
            filter_fn = self.filters[delta]
        else:            
            try: 
                filter_fn = self.filters[filter]
            except KeyError: 
                filter = "day"
                filter_fn = self.filters['day']
            
            if not to_date:
                to_date = timezone.now()
            
            if not from_date:
                from_date = to_date - timezone.timedelta(days=self.days[filter])
        
        qs = qs.filter(created_at__gte=from_date, created_at__lte=to_date)

        labels, qs, fmt = filter_fn(annotations, qs, from_date, to_date)

        datasets = []
        for i, (_, action) in enumerate(FilterActionChoices.choices):
            dataset = {'label': action, 'data': []}
            data_points = {request[0]: request[i + 1] for request in qs}

            for point in labels:
                dataset['data'].append({
                    'x': fmt(point),
                    'y': data_points.get(point, 0),
                })

            datasets.append(dataset)

        labels = [fmt(point) for point in labels]

        chart = {
            "datasets": datasets,
            "labels": labels,
        }
        return self.render_to_response(
            context=self.get_context_data(
                chart = chart,
                query_by = filter,
                filter = dj_filter,
                chart_type = dj_filter.form.cleaned_data.get('chart_type', 'line'),
            ),
        )


@hooks.register("register_admin_urls")
def register_admin_urls():
    return [
        path('filter-chart/', FilteredRequestChartView.as_view(), name='filter_chart_view'),
    ]


from wagtail.contrib.settings.registry import SettingMenuItem
from wagtail.admin.menu import (
    Menu, SubmenuMenuItem, MenuItem,
)


filters_menu = Menu(
    register_hook_name='register_filters_menu_item',
    construct_hook_name='construct_filters_menu',
)


@hooks.register('register_filters_menu_item')
def register_filters_menu_item():
    return MenuItem(
        _('Analyse'),
        url=reverse('filter_chart_view'),
        icon_name="filters-chart",
        order=1,
    )

@hooks.register('register_filters_menu_item')
def register_settings_menu_item():
    return SettingMenuItem(
        model=FilterSettings,
        icon="sliders",
        name="request_filters_settings",
        order=2,
    )

@hooks.register('register_filters_menu_item')
def register_settings_menu_item():
    return FilteredRequestViewSet().get_menu_item(
        order=3,
    )

@hooks.register(RequestFilters.REGISTER_TO_MENU)
def register_admin_menu_item():
    return SubmenuMenuItem(
        _('Request Filters'),
        icon_name='filters-firewall',
        name='request_filters',
        menu=filters_menu,
        order=100,
    )

@hooks.register("register_icons")
def register_icons(icons):
    return icons + [
        'request_filters/filters-chart.svg',
        'request_filters/filters-firewall.svg',
    ]

@hooks.register("construct_settings_menu")
def construct_settings_menu(request, items):
    items[:] = [item for item in items if item.name != "request_filters_settings_disabled"]
    return items

@hooks.register('register_permissions')
def register_permissions():
    return Permission.objects.filter(
        content_type__app_label=FilteredRequest._meta.app_label,
        codename__in=[
            "see_chart",
        ]
    )
