#
# Copyright 2016,2017,2018
# - Skia <skia@libskia.so>
# - Sli <antoine@bartuccio.fr>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
import logging
import math
from functools import partial

from django import forms
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db import IntegrityError
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.utils import html, timezone
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView, RedirectView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from haystack.query import RelatedSearchQuerySet
from honeypot.decorators import check_honeypot

from club.widgets.ajax_select import AutoCompleteSelectClub
from core.auth.mixins import (
    CanEditMixin,
    CanEditPropMixin,
    CanViewMixin,
    can_view,
)
from core.views.widgets.ajax_select import (
    AutoCompleteSelect,
    AutoCompleteSelectMultipleGroup,
)
from core.views.widgets.markdown import MarkdownInput
from forum.models import Forum, ForumMessage, ForumMessageMeta, ForumTopic


class ForumSearchView(ListView):
    template_name = "forum/search.jinja"

    def get_queryset(self):
        query = self.request.GET.get("query", "")
        order_by = self.request.GET.get("order", "")

        try:
            queryset = (
                RelatedSearchQuerySet()
                .models(ForumMessage)
                .autocomplete(auto=html.escape(query))
            )
        except TypeError:
            return []

        if order_by == "date":
            queryset = queryset.order_by("-date")

        queryset = queryset.load_all()
        queryset = queryset.load_all_queryset(
            ForumMessage,
            ForumMessage.objects.all()
            .prefetch_related("topic__forum__edit_groups")
            .prefetch_related("topic__forum__view_groups")
            .prefetch_related("topic__forum__owner_club"),
        )

        # Filter unauthorized responses
        resp = []
        count = 0
        max_count = 30
        for r in queryset:
            if count >= max_count:
                return resp
            if can_view(r.object, self.request.user) and can_view(
                r.object.topic, self.request.user
            ):
                resp.append(r.object)
                count += 1
        return resp


class ForumMainView(ListView):
    queryset = Forum.objects.filter(parent=None).prefetch_related(
        "children___last_message__author", "children___last_message__topic"
    )
    template_name = "forum/main.jinja"


class ForumMarkAllAsRead(RedirectView):
    permanent = False
    url = reverse_lazy("forum:last_unread")

    def get(self, request, *args, **kwargs):
        fi = request.user.forum_infos
        fi.last_read_date = timezone.now()
        fi.save()
        try:
            for m in request.user.read_messages.filter(date__lt=fi.last_read_date):
                m.readers.remove(request.user)  # Clean up to keep table low in data
        except IntegrityError:
            pass
        return super().get(request, *args, **kwargs)


class ForumFavoriteTopics(ListView):
    model = ForumTopic
    template_name = "forum/favorite_topics.jinja"
    paginate_by = settings.SITH_FORUM_PAGE_LENGTH / 2

    def get_queryset(self):
        topic_list = self.request.user.favorite_topics.all()
        return topic_list


class ForumLastUnread(ListView):
    model = ForumTopic
    template_name = "forum/last_unread.jinja"
    paginate_by = settings.SITH_FORUM_PAGE_LENGTH / 2

    def get_queryset(self):
        topic_list = (
            self.model.objects.filter(
                _last_message__date__gt=self.request.user.forum_infos.last_read_date
            )
            .exclude(_last_message__readers=self.request.user)
            .order_by("-_last_message__date")
            .select_related("_last_message__author", "author")
            .prefetch_related("forum__edit_groups")
        )
        return topic_list


class ForumNameField(forms.ModelChoiceField):
    def label_from_instance(self, obj):
        return obj.get_full_name()


class ForumForm(forms.ModelForm):
    class Meta:
        model = Forum
        fields = [
            "name",
            "parent",
            "number",
            "owner_club",
            "is_category",
            "edit_groups",
            "view_groups",
        ]
        widgets = {
            "edit_groups": AutoCompleteSelectMultipleGroup,
            "view_groups": AutoCompleteSelectMultipleGroup,
            "owner_club": AutoCompleteSelectClub,
        }

    parent = ForumNameField(
        Forum.objects.all(), widget=AutoCompleteSelect, required=False
    )


class ForumCreateView(UserPassesTestMixin, CreateView):
    model = Forum
    form_class = ForumForm
    template_name = "core/create.jinja"

    def test_func(self):
        if self.request.user.has_perm("forum.add_forum"):
            return True
        parent = Forum.objects.filter(id=self.request.GET["parent"]).first()
        if parent is not None:
            return self.request.user.is_owner(parent)
        return False

    def get_initial(self):
        init = super().get_initial()
        parent = Forum.objects.filter(id=self.request.GET["parent"]).first()
        if parent is not None:
            init["parent"] = parent
            init["owner_club"] = parent.owner_club
            init["edit_groups"] = parent.edit_groups.all()
            init["view_groups"] = parent.view_groups.all()
        return init


class ForumEditForm(ForumForm):
    recursive = forms.BooleanField(
        label=_("Apply rights and club owner recursively"), required=False
    )


class ForumEditView(CanEditPropMixin, UpdateView):
    model = Forum
    pk_url_kwarg = "forum_id"
    form_class = ForumEditForm
    template_name = "core/edit.jinja"
    success_url = reverse_lazy("forum:main")

    def form_valid(self, form):
        ret = super().form_valid(form)
        if form.cleaned_data["recursive"]:
            self.object.apply_rights_recursively()
        return ret


class ForumDeleteView(CanEditPropMixin, DeleteView):
    model = Forum
    pk_url_kwarg = "forum_id"
    template_name = "core/delete_confirm.jinja"
    success_url = reverse_lazy("forum:main")


class ForumDetailView(CanViewMixin, DetailView):
    model = Forum
    template_name = "forum/forum.jinja"
    pk_url_kwarg = "forum_id"

    def get_context_data(self, **kwargs):
        kwargs = super().get_context_data(**kwargs)
        qs = (
            self.object.topics.order_by("-_last_message__date")
            .select_related("_last_message__author", "author")
            .prefetch_related("forum__edit_groups")
        )
        paginator = Paginator(qs, settings.SITH_FORUM_PAGE_LENGTH)
        page = self.request.GET.get("topic_page")
        try:
            kwargs["topics"] = paginator.page(page)
        except PageNotAnInteger:
            kwargs["topics"] = paginator.page(1)
        except EmptyPage:
            kwargs["topics"] = paginator.page(paginator.num_pages)
        return kwargs


class TopicForm(forms.ModelForm):
    class Meta:
        model = ForumMessage
        fields = ["title", "message"]
        widgets = {"message": MarkdownInput}

    title = forms.CharField(required=True, label=_("Title"))


@method_decorator(
    partial(check_honeypot, field_name=settings.HONEYPOT_FIELD_NAME_FORUM), name="post"
)
class ForumTopicCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
    model = ForumMessage
    form_class = TopicForm
    template_name = "forum/reply.jinja"

    @cached_property
    def forum(self):
        return get_object_or_404(Forum, id=self.kwargs["forum_id"], is_category=False)

    def test_func(self):
        return self.request.user.has_perm("forum.add_forumtopic") or (
            self.request.user.can_view(self.forum)
        )

    def form_valid(self, form):
        topic = ForumTopic(
            _title=form.instance.title, author=self.request.user, forum=self.forum
        )
        topic.save()
        form.instance.topic = topic
        form.instance.author = self.request.user
        return super().form_valid(form)


class ForumTopicEditView(CanEditMixin, UpdateView):
    model = ForumTopic
    fields = ["forum"]
    pk_url_kwarg = "topic_id"
    template_name = "core/edit.jinja"


class ForumTopicSubscribeView(
    LoginRequiredMixin, CanViewMixin, SingleObjectMixin, RedirectView
):
    model = ForumTopic
    pk_url_kwarg = "topic_id"
    permanent = False

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        if self.object.subscribed_users.filter(id=request.user.id).exists():
            self.object.subscribed_users.remove(request.user)
        else:
            self.object.subscribed_users.add(request.user)
        return super().get(request, *args, **kwargs)

    def get_redirect_url(self, *args, **kwargs):
        return self.object.get_absolute_url()


class ForumTopicDetailView(CanViewMixin, DetailView):
    model = ForumTopic
    pk_url_kwarg = "topic_id"
    template_name = "forum/topic.jinja"
    context_object_name = "topic"
    queryset = ForumTopic.objects.select_related("forum__parent")

    def get_context_data(self, **kwargs):
        topic: ForumTopic = self.object
        kwargs = super().get_context_data(**kwargs)
        msg = topic.get_first_unread_message(self.request.user)
        if msg is None:
            kwargs["first_unread_message_id"] = math.inf
        else:
            kwargs["first_unread_message_id"] = msg.id
        paginator = Paginator(
            topic.messages.select_related("author__avatar_pict", "topic__forum")
            .prefetch_related("topic__forum__edit_groups", "readers")
            .order_by("date"),
            settings.SITH_FORUM_PAGE_LENGTH,
        )
        page = self.request.GET.get("page")
        try:
            kwargs["msgs"] = paginator.page(page)
        except PageNotAnInteger:
            kwargs["msgs"] = paginator.page(1)
        except EmptyPage:
            kwargs["msgs"] = paginator.page(paginator.num_pages)
        return kwargs


class ForumMessageView(SingleObjectMixin, RedirectView):
    model = ForumMessage
    pk_url_kwarg = "message_id"
    permanent = False

    def get_redirect_url(self, *args, **kwargs):
        self.object = self.get_object()
        return self.object.get_url()


@method_decorator(
    partial(check_honeypot, field_name=settings.HONEYPOT_FIELD_NAME_FORUM), name="post"
)
class ForumMessageEditView(CanEditMixin, UpdateView):
    model = ForumMessage
    form_class = forms.modelform_factory(
        model=ForumMessage,
        fields=["title", "message"],
        widgets={"message": MarkdownInput},
    )
    template_name = "forum/reply.jinja"
    pk_url_kwarg = "message_id"

    def form_valid(self, form):
        ForumMessageMeta(
            message=self.object, user=self.request.user, action="EDIT"
        ).save()
        return super().form_valid(form)

    def get_context_data(self, **kwargs):
        kwargs = super().get_context_data(**kwargs)
        kwargs["topic"] = self.object.topic
        return kwargs


class ForumMessageDeleteView(SingleObjectMixin, RedirectView):
    model = ForumMessage
    pk_url_kwarg = "message_id"
    permanent = False

    def get_redirect_url(self, *args, **kwargs):
        self.object = self.get_object()
        if self.object.can_be_moderated_by(self.request.user):
            ForumMessageMeta(
                message=self.object, user=self.request.user, action="DELETE"
            ).save()
        return self.object.get_absolute_url()


class ForumMessageUndeleteView(SingleObjectMixin, RedirectView):
    model = ForumMessage
    pk_url_kwarg = "message_id"
    permanent = False

    def get_redirect_url(self, *args, **kwargs):
        self.object = self.get_object()
        if self.object.can_be_moderated_by(self.request.user):
            ForumMessageMeta(
                message=self.object, user=self.request.user, action="UNDELETE"
            ).save()
        return self.object.get_absolute_url()


@method_decorator(
    partial(check_honeypot, field_name=settings.HONEYPOT_FIELD_NAME_FORUM), name="post"
)
class ForumMessageCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
    model = ForumMessage
    form_class = forms.modelform_factory(
        model=ForumMessage,
        fields=["title", "message"],
        widgets={"message": MarkdownInput},
    )
    template_name = "forum/reply.jinja"

    @cached_property
    def topic(self):
        return get_object_or_404(ForumTopic, id=self.kwargs["topic_id"])

    def test_func(self):
        return self.request.user.has_perm(
            "forum.add_forummessage"
        ) or self.request.user.can_view(self.topic)

    def get_initial(self):
        init = super().get_initial()
        try:
            message = (
                ForumMessage.objects.select_related("author")
                .filter(id=self.request.GET["quote_id"])
                .first()
            )
            init["message"] = "> ##### %s\n" % (
                _("%(author)s said") % {"author": message.author.get_short_name()}
            )
            init["message"] += "\n".join(
                ["> " + line for line in message.message.split("\n")]
            )
            init["message"] += "\n\n"
        except Exception as e:
            logging.error(e)
        return init

    def form_valid(self, form):
        form.instance.topic = self.topic
        form.instance.author = self.request.user
        return super().form_valid(form)

    def get_context_data(self, **kwargs):
        kwargs = super().get_context_data(**kwargs)
        kwargs["topic"] = self.topic
        return kwargs
