import django_filters
from django.utils.translation import gettext_lazy as _
from django import forms
from django.contrib.postgres.search import SearchQuery, SearchHeadline
from django.db.models import Count, Q, F
from datetime import date, timedelta
from urllib.parse import urlparse, parse_qs, urlencode

from django.http import QueryDict
from django.contrib.gis.measure import D

from django.forms import (
    ModelForm,
    ValidationError,
    TextInput,
    Form,
    URLField,
    MultipleHiddenInput,
    Textarea,
    CharField,
    ChoiceField,
    RadioSelect,
    MultipleChoiceField,
    BooleanField,
    HiddenInput,
    ModelChoiceField,
)

from .forms import (
    URLSubmissionForm,
    EventForm,
    BatchImportationForm,
    FixDuplicates,
    SelectEventInList,
    MergeDuplicates,
    RecurrentImportForm,
    CategorisationRuleImportForm,
    CategorisationForm,
    EventAddPlaceForm,
    PlaceForm,
)

from .models import (
    ReferenceLocation,
    RecurrentImport,
    Tag,
    Event,
    Category,
    Message,
    DuplicatedEvents
)


class EventFilter(django_filters.FilterSet):
    RECURRENT_CHOICES = [
        ("remove_recurrent", "Masquer les événements récurrents"),
        ("only_recurrent", "Montrer uniquement les événements récurrents"),
    ]

    DISTANCE_CHOICES = [5, 10, 15, 30]

    position = django_filters.ModelChoiceFilter(
        label="À proximité de",
        method="no_filter",
        empty_label=_("Select a location"),
        to_field_name='slug',
        queryset=ReferenceLocation.objects.filter(main__gt=0).order_by("-main", "name__unaccent")
    )

    radius = django_filters.ChoiceFilter(
        label="Dans un rayon de",
        method="no_filter",
        choices=[(x, str(x) + " km") for x in DISTANCE_CHOICES],
        null_label=None,
        empty_label=None
    )

    exclude_tags = django_filters.MultipleChoiceFilter(
        label="Exclure les étiquettes",
        choices=[],
        lookup_expr="icontains",
        field_name="tags",
        exclude=True,
        widget=forms.SelectMultiple,
    )

    tags = django_filters.MultipleChoiceFilter(
        label="Inclure les étiquettes",
        choices=[],
        lookup_expr="icontains",
        conjoined=True,
        field_name="tags",
        widget=forms.SelectMultiple,
    )

    recurrences = django_filters.ChoiceFilter(
        label="Inclure la récurrence",
        choices=RECURRENT_CHOICES,
        method="filter_recurrences",
    )

    status = django_filters.MultipleChoiceFilter(
        label="Filtrer par status",
        choices=Event.STATUS.choices,
        field_name="status",
        widget=forms.CheckboxSelectMultiple,
    )

    class Meta:
        model = Event
        fields = ["tags", "exclude_tags", "status", "recurrences"]

    def __init__(self, *args, **kwargs):
        # urls were using pk, now we moved to slug
        if len(args) > 0 and "position" in args[0] and args[0]["position"].isdigit():
            args[0]._mutable = True
            el = ReferenceLocation.objects.filter(pk=int(args[0]["position"])).values("slug").first()
            args[0]["position"] = None if el is None else el["slug"]
            args[0]._mutable = False

        super().__init__(*args, **kwargs)
        if not kwargs["request"].user.is_authenticated:
            self.form.fields.pop("status")
        self.form.fields["exclude_tags"].choices = Tag.get_tag_groups(exclude=True, nb_suggestions=0)
        self.form.fields["tags"].choices = Tag.get_tag_groups(include=True)

    def has_category_parameters(self):
        url = self.request.get_full_path()
        return "category=" in url and not url.startswith("/cat:")

    def get_new_url(self):
        url = self.request.get_full_path()
        if url.startswith("/cat:"):
            return url
        else:
            parsed_url = urlparse(url)
            params = parse_qs(parsed_url.query)
            if len(params['category']) == 0:
                return url
            else:
                cat_id = params['category'][0]
                del params["category"]
                url = parsed_url._replace(query=urlencode(params, doseq=True)).geturl()
                if cat_id.isdigit():
                    category = Category.objects.filter(pk=cat_id).first()
                    if category is None:
                        return url
                    else:
                        return "/cat:" + category.slug + url
                else:
                    return url


    def filter_recurrences(self, queryset, name, value):
        # construct the full lookup expression
        lookup = "__".join([name, "isnull"])
        return queryset.filter(**{lookup: value == "remove_recurrent"})

    def no_filter(self, queryset, name, value):
        return queryset

    @property
    def qs(self):
        parent = super().qs
        if self.get_cleaned_data("position") is None or self.get_cleaned_data("radius") is None:
            return parent
        d = self.get_cleaned_data("radius")
        p = self.get_cleaned_data("position")
        if not isinstance(d, str) or not isinstance(p, ReferenceLocation):
            return parent
        try:
            d = float(d)
        except ValueError:
            return parent
        if d <= 0:
            return parent

        p = p.location

        return parent.exclude(exact_location=False).filter(exact_location__location__distance_lt=(p, D(km=d)))

    def has_location(self):
        d = self.get_cleaned_data("radius")
        p = self.get_cleaned_data("position")
        if not isinstance(d, str) or not isinstance(p, ReferenceLocation):
            return False
        try:
            d = float(d)
        except ValueError:
            return False
        if d <= 0:
            return False

        return True

    def get_url(self):
        if isinstance(self.form.data, QueryDict):
            return self.form.data.urlencode()
        else:
            return ""

    def get_full_url(self):
        return self.request.get_full_path()

    def get_url_without_filters(self):
        return self.request.get_full_path().split("?")[0]

    def get_cleaned_data(self, name):

        try:
            return self.form.cleaned_data[name]
        except AttributeError:
            return {}
        except KeyError:
            return {}

    def get_tags(self):
        return self.get_cleaned_data("tags")

    def get_exclude_tags(self):
        return self.get_cleaned_data("exclude_tags")

    def get_status(self):
        return self.get_cleaned_data("status")

    def get_position(self):
        return self.get_cleaned_data("position")

    def get_radius(self):
        return self.get_cleaned_data("radius")

    def to_str(self, prefix=''):
        self.form.full_clean()
        result = ' '.join([t for t in self.get_tags()] + ["~" + t for t in self.get_exclude_tags()]  + [str(self.get_position()), str(self.get_radius())])
        if len(result) > 0:
            result = prefix + result
        return result

    def get_status_names(self):
        if "status" in self.form.cleaned_data:
            return [
                dict(Event.STATUS.choices)[s] for s in self.get_cleaned_data("status")
            ]
        else:
            return []

    def get_recurrence_filtering(self):
        if "recurrences" in self.form.cleaned_data:
            d = dict(self.RECURRENT_CHOICES)
            v = self.form.cleaned_data["recurrences"]
            if v in d:
                return d[v]
            else:
                return ""
        else:
            return ""

    def is_resetable(self):
        if self.request.user.is_authenticated:
            if (
                len(self.get_cleaned_data("status")) != 1
                or
                self.get_cleaned_data("status")[0] != Event.STATUS.PUBLISHED
            ):
                return True
        else:
            if (
                len(self.get_cleaned_data("status")) != 0
            ):
                return True
        return (
            len(self.get_cleaned_data("tags")) != 0
            or len(self.get_cleaned_data("exclude_tags")) != 0
            or len(self.get_cleaned_data("recurrences")) != 0
            or ((not self.get_cleaned_data("position") is None) and (not self.get_cleaned_data("radius") is None))
        )

    def is_active(self, only_categories=False):
        if only_categories:
            return len(self.get_cleaned_data("category")) != 0
        else:
            return (
                len(self.get_cleaned_data("status")) != 0
                or len(self.get_cleaned_data("tags")) != 0
                or len(self.get_cleaned_data("exclude_tags")) != 0
                or len(self.get_cleaned_data("recurrences")) != 0
                or ((not self.get_cleaned_data("position") is None) and (not self.get_cleaned_data("radius") is None))
            )

    def is_selected_tag(self, tag):
        return "tags" in self.form.cleaned_data and tag in self.form.cleaned_data["tags"]

    def get_url_add_tag(self, tag):
        full_path = self.request.get_full_path()

        result = full_path + ('&' if '?' in full_path else '?') + 'tags=' + str(tag)

        return result

    def tag_exists(self, tag):
        return tag in [t[0] for g in self.form.fields["tags"].choices for t in g[1]]

    def set_default_values(request):
        if request.user.is_authenticated:
            if request.GET.get('status', None) is None:
                tempdict = request.GET.copy()
                tempdict['status'] = 'published'
                request.GET = tempdict
                return request
        return request

    def get_position_radius(self):
        if self.get_cleaned_data("position") is None or self.get_cleaned_data("radius") is None:
            return ""
        else:
            return str(self.get_cleaned_data("position")) + ' (' + str(self.get_cleaned_data("radius")) + ' km)'

    def is_filtered_by_position_radius(self):
        return not self.get_cleaned_data("position") is None and not self.get_cleaned_data("radius") is None

    def get_url_add_suggested_position(self, location):
        result = self.request.get_full_path()
        return result + ('&' if '?' in result else '?') + 'position=' + str(location.slug) + "&radius=" + str(location.suggested_distance)


class EventFilterAdmin(django_filters.FilterSet):
    status = django_filters.MultipleChoiceFilter(
        choices=Event.STATUS.choices, widget=forms.CheckboxSelectMultiple
    )

    representative = django_filters.MultipleChoiceFilter(
        label=_("Representative version"),
        choices=[(True, _("Yes")), (False, _("No"))], 
        method="filter_by_representative",
        widget=forms.CheckboxSelectMultiple)

    pure_import = django_filters.MultipleChoiceFilter(
        label=_("Pure import"),
        choices=[(True, _("Yes")), (False, _("No"))], 
        method="filter_by_pure_import",
        widget=forms.CheckboxSelectMultiple)

    in_recurrent_import = django_filters.MultipleChoiceFilter(
        label=_("In recurrent import"),
        choices=[(True, _("Yes")), (False, _("No"))], 
        method="filter_by_in_recurrent_import",
        widget=forms.CheckboxSelectMultiple)

    o = django_filters.ChoiceFilter(
        label=_("Sort by"),
        choices=[
            ("moderated_date", _("last moderated first")),
            ("modified_date", _("last modified first")),
            ("imported_date", _("last imported first")),
            ("created_date", _("last created first")),
            ], 
        method="sort_on_date")

    import_sources = django_filters.ModelChoiceFilter(
        label=_("Imported from"),
        method="filter_by_source",
        queryset=RecurrentImport.objects.all().order_by("name__unaccent")
    )

    def sort_on_date(self, queryset, name, value):
        print(name, value)
        if value in ['created_date', 'imported_date', 'modified_date', 'moderated_date']:
            notnull = value + '__isnull'
            return queryset.filter(**{notnull: False}).order_by('-' + value)
        else:
            return queryset

    def filter_by_source(self, queryset, name, value):
        src = RecurrentImport.objects.get(pk=value.pk).source
        return queryset.filter(import_sources__contains=[src])

    def filter_by_in_recurrent_import(self, queryset, name, value):
        if value is None or len(value) != 1:
            return queryset
        else:
            srcs = RecurrentImport.objects.all().values_list("source")
            q = Q(import_sources__overlap=srcs)
            if value[0] == 'True':
                print(q)
                return queryset.filter(q)
            else:
                return queryset.exclude(q)

    def filter_by_pure_import(self, queryset, name, value):
        if value is None or len(value) != 1:
            return queryset
        else:
            q = (Q(import_sources__isnull=False) &
                 (Q(modified_date__isnull=True) |
                 Q(modified_date__lte=F('imported_date'))))
            if value[0] == 'True':
                print(q)
                return queryset.filter(q)
            else:
                return queryset.exclude(q)


    def filter_by_representative(self, queryset, name, value):
        if value is None or len(value) != 1:
            return queryset
        else:
            q = (Q(other_versions__isnull=True) |
                 Q(other_versions__representative=F('pk')) |
                 Q(other_versions__representative__isnull=True))
            if value[0] == 'True':
                return queryset.filter(q)
            else:
                return queryset.exclude(q)


    class Meta:
        model = Event
        fields = ["status"]


class MessagesFilterAdmin(django_filters.FilterSet):
    closed = django_filters.MultipleChoiceFilter(
        label=_("Status"),
        choices=((True, _("Closed")), (False, _("Open"))),
        widget=forms.CheckboxSelectMultiple,
    )
    spam = django_filters.MultipleChoiceFilter(
        label=_("Spam"),
        choices=((True, _("Spam")), (False, _("Non spam"))),
        widget=forms.CheckboxSelectMultiple,
    )

    message_type = django_filters.MultipleChoiceFilter(
        label=_("Type"),
        choices=Message.TYPE.choices,
        widget=forms.CheckboxSelectMultiple,
    )

    class Meta:
        model = Message
        fields = ["closed", "spam", "message_type"]

    def is_contact_messages(self):
        return "message_type" in self.form.cleaned_data and "contact_form" in self.form.cleaned_data["message_type"]


class SimpleSearchEventFilter(django_filters.FilterSet):
    q = django_filters.CharFilter(method="custom_filter", 
        label=_("Search"),
        widget=forms.TextInput(attrs={"type": "search"})
    )

    status = django_filters.MultipleChoiceFilter(
        label="Filtrer par status",
        choices=Event.STATUS.choices,
        field_name="status",
        widget=forms.CheckboxSelectMultiple,
    )

    past = django_filters.ChoiceFilter(
            label=_("In the past"),
            choices=[(False, _("No")), (True, _("Yes"))], 
            null_label=None,
            empty_label=None,
            method="in_past",
            widget=forms.Select)

    def in_past(self, queryset, name, value):
        if value and value == "True":
            now = date.today()
            qs = queryset.filter(start_day__lt=now).order_by("-start_day", "-start_time")
        else:
            start = date.today() + timedelta(days=-2)
            qs = queryset.filter(start_day__gte=start).order_by("start_day", "start_time")
        return qs


    def custom_filter(self, queryset, name, value):
        search_query = SearchQuery(value, config="french")
        qs = queryset.filter(
            Q(title__icontains=value)
            | Q(category__name__icontains=value)
            | Q(tags__icontains=[value])
            | Q(exact_location__name__icontains=value)
            | Q(description__icontains=value)
        )
        for f in ["title", "category__name", "exact_location__name", "description"]:
            params = {
                f
                + "_hl": SearchHeadline(
                    f,
                    search_query,
                    start_sel='<span class="highlight">',
                    stop_sel="</span>",
                    config="french",
                )
            }
            qs = qs.annotate(**params)
        return qs

    class Meta:
        model = Event
        fields = ["q"]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if not kwargs["request"].user.is_authenticated:
            self.form.fields.pop("status")



class SearchEventFilter(django_filters.FilterSet):
    tags = django_filters.CharFilter(lookup_expr="icontains")
    title = django_filters.CharFilter(method="hl_filter_contains")
    location = django_filters.CharFilter(method="hl_filter_contains")
    description = django_filters.CharFilter(method="hl_filter_contains")
    start_day = django_filters.DateFromToRangeFilter(
        widget=django_filters.widgets.RangeWidget(attrs={"type": "date"})
    )
    status = django_filters.MultipleChoiceFilter(
        label="Filtrer par status",
        choices=Event.STATUS.choices,
        field_name="status",
        widget=forms.CheckboxSelectMultiple,
    )

    o = django_filters.OrderingFilter(
        # tuple-mapping retains order
        fields=(
            ("title", "title"),
            ("description", "description"),
            ("start_day", "start_day"),
        ),
    )

    def hl_filter_contains(self, queryset, name, value):
        # first check if it contains
        filter_contains = {name + "__contains": value}
        queryset = queryset.filter(**filter_contains)

        # then hightlight the result
        search_query = SearchQuery(value, config="french")
        params = {
            name
            + "_hl": SearchHeadline(
                name,
                search_query,
                start_sel='<span class="highlight">',
                stop_sel="</span>",
                config="french",
            )
        }
        return queryset.annotate(**params)

    class Meta:
        model = Event
        fields = ["title", "location", "description", "category", "tags", "start_day"]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if not kwargs["request"].user.is_authenticated:
            self.form.fields.pop("status")


class DuplicatedEventsFilter(django_filters.FilterSet):
    fixed = django_filters.BooleanFilter(
        label="Résolu",
        field_name='representative', method="fixed_qs")

    class Meta:
        model = DuplicatedEvents
        fields = []

    def get_cleaned_data(self, name):
        try:
            return self.form.cleaned_data[name]
        except AttributeError:
            return {}
        except KeyError:
            return {}

    def get_fixed_url(self):
        if "fixed" in self.form.cleaned_data:
            if self.form.cleaned_data["fixed"] is None:
                return "fixed=unknown"
            else:
                if self.form.cleaned_data["fixed"]:
                    return "fixed=true"
                else:
                    return "fixed=false" 
        else:
            return ""
        
    

    def fixed_qs(self, queryset, name, value):
        return DuplicatedEvents.not_fixed_qs(queryset, value)


class RecurrentImportFilter(django_filters.FilterSet):

    name = django_filters.ModelMultipleChoiceFilter(
        label="Filtrer par nom",
        field_name="name",
        queryset=RecurrentImport.objects.all().order_by("name__unaccent")
    )

    class Meta:
        model = RecurrentImport
        fields = ["name"]

