__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"


import datetime
import json

from django import forms
from django.contrib import messages
from django.contrib.auth.models import User, Group
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.core import serializers
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.views.generic.list import ListView

from guardian.decorators import permission_required_or_403
from guardian.mixins import PermissionRequiredMixin
from guardian.shortcuts import (
    assign_perm,
    remove_perm,
    get_objects_for_user,
    get_perms,
    get_users_with_perms,
    get_groups_with_perms,
)

from .models import Forum, Meeting, Post, Motion
from .forms import (
    ForumForm,
    ForumGroupPermissionsForm,
    ForumOrganizationPermissionsForm,
    MeetingForm,
    PostForm,
    MotionForm,
    MotionVoteForm,
)

from scipost.mixins import PermissionsMixin


class ForumCreateView(PermissionsMixin, CreateView):
    permission_required = "forums.add_forum"
    model = Forum
    form_class = ForumForm
    template_name = "forums/forum_form.html"
    success_url = reverse_lazy("forums:forums")

    def get_initial(self):
        initial = super().get_initial()
        parent_model = self.kwargs.get("parent_model")
        parent_content_type = None
        parent_object_id = self.kwargs.get("parent_id")
        if parent_model == "forum":
            parent_content_type = ContentType.objects.get(
                app_label="forums", model="forum"
            )
        initial.update(
            {
                "moderators": self.request.user,
                "parent_content_type": parent_content_type,
                "parent_object_id": parent_object_id,
            }
        )
        return initial


class MeetingCreateView(ForumCreateView):
    model = Meeting
    form_class = MeetingForm


class ForumUpdateView(PermissionRequiredMixin, UpdateView):
    permission_required = "forums.update_forum"
    template_name = "forums/forum_form.html"

    def get_object(self, queryset=None):
        try:
            return Meeting.objects.get(slug=self.kwargs["slug"])
        except Meeting.DoesNotExist:
            return Forum.objects.get(slug=self.kwargs["slug"])

    def get_form(self, form_class=None):
        try:
            self.object.meeting
            return MeetingForm(**self.get_form_kwargs())
        except Meeting.DoesNotExist:
            return ForumForm(**self.get_form_kwargs())


class ForumDeleteView(PermissionRequiredMixin, DeleteView):
    permission_required = "forums.delete_forum"
    model = Forum
    success_url = reverse_lazy("forums:forums")

    def delete(self, request, *args, **kwargs):
        """
        A Forum can only be deleted if it does not have any descendants.
        Upon deletion, all object-level permissions associated to the
        Forum are explicitly removed, to avoid orphaned permissions.
        """
        forum = get_object_or_404(Forum, slug=self.kwargs.get("slug"))
        groups_perms_dict = get_groups_with_perms(forum, attach_perms=True)
        if forum.child_forums.all().count() > 0:
            messages.warning(request, "A Forum with descendants cannot be deleted.")
            return redirect(forum.get_absolute_url())
        for group, perms_list in groups_perms_dict.items():
            for perm in perms_list:
                remove_perm(perm, group, forum)
        return super().delete(request, *args, **kwargs)


class ForumDetailView(PermissionRequiredMixin, DetailView):
    permission_required = "forums.can_view_forum"
    model = Forum
    template_name = "forums/forum_detail.html"

    def get_queryset(self):
        qs = super().get_queryset()
        qs = qs.select_related(
            "meeting",
        )
        qs = qs.prefetch_related(
            "parent",
            "child_forums",
            "posts__motion",
            "posts__posted_by",
            "motions__posted_by",
            "motions__in_agreement",
            "motions__in_doubt",
            "motions__in_disagreement",
            "motions__in_abstain",
            "motions__eligible_for_voting",
        )
        return qs


class HX_ForumQuickLinksAllView(PermissionRequiredMixin, DetailView):
    permission_required = "forums.can_view_forum"
    model = Forum
    template_name = "forums/_hx_forum_quick_links_all.html"

    def get_queryset(self):
        qs = super().get_queryset()
        qs = qs.prefetch_related(
            "posts_all__posted_by",
            "posts__motion",
        )
        return qs


class HX_ForumQuickLinksFollowupsView(PermissionRequiredMixin, DetailView):
    permission_required = "forums.can_view_forum"
    model = Forum
    template_name = "forums/_hx_forum_quick_links_followups.html"

    def get_queryset(self):
        qs = super().get_queryset()
        qs = qs.prefetch_related(
            "posts__posted_by",
            "posts__cf_latest_followup_in_hierarchy__posted_by",
        )
        return qs


class HX_ForumPermissionsView(PermissionRequiredMixin, DetailView):
    permission_required = "forums.add_forum"
    model = Forum
    template_name = "forums/_hx_forum_permissions.html"

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)
        context["groups_with_perms"] = get_groups_with_perms(self.object).order_by(
            "name"
        )
        context["users_with_perms"] = get_users_with_perms(
            self.object,
            attach_perms=True,
        )
        return context


class ForumPermissionsView(PermissionRequiredMixin, UpdateView):
    permission_required = "forums.can_administer_forum"
    model = Forum
    form_class = ForumGroupPermissionsForm
    template_name = "forums/forum_permissions.html"

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)
        try:
            context["group"] = Group.objects.get(pk=self.kwargs.get("group_id"))
        except Group.DoesNotExist:
            pass
        return context

    def get_initial(self, *args, **kwargs):
        initial = super().get_initial(*args, **kwargs)
        try:
            group = Group.objects.get(pk=self.kwargs.get("group_id"))
            perms = get_perms(group, self.object)
            initial["groups"] = group.id
            initial["can_administer"] = "can_administer_forum" in perms
            initial["can_view"] = "can_view_forum" in perms
            initial["can_post"] = "can_post_to_forum" in perms
        except Group.DoesNotExist:
            pass
        return initial

    def form_valid(self, form):
        for group in form.cleaned_data["groups"]:
            if form.cleaned_data["can_administer"]:
                assign_perm("can_administer_forum", group, self.object)
            else:
                remove_perm("can_administer_forum", group, self.object)
            if form.cleaned_data["can_view"]:
                assign_perm("can_view_forum", group, self.object)
            else:
                remove_perm("can_view_forum", group, self.object)
            if form.cleaned_data["can_post"]:
                assign_perm("can_post_to_forum", group, self.object)
            else:
                remove_perm("can_post_to_forum", group, self.object)
        return super().form_valid(form)


class ForumListView(LoginRequiredMixin, ListView):
    model = Forum
    template_name = "forum_list.html"

    def get_queryset(self):
        queryset = get_objects_for_user(
            self.request.user, "forums.can_view_forum"
        ).anchors().select_related("meeting").prefetch_related(
            "posts" + "__followup_posts" * 3,
            "child_forums__posts" + "__followup_posts" * 7,
        )
        return queryset


@permission_required_or_403("forums.can_post_to_forum", (Forum, "slug", "slug"))
def _hx_post_form_button(request, slug, parent_model, parent_id, origin, target, text):
    context = {
        "slug": slug,
        "parent_model": parent_model,
        "parent_id": parent_id,
        "origin": origin,
        "target": target,
        "text": text,
    }
    return render(request, "forums/_hx_post_form_button.html", context)


@permission_required_or_403("forums.can_post_to_forum", (Forum, "slug", "slug"))
def _hx_post_form(request, slug, parent_model, parent_id, origin, target, text):
    forum = get_object_or_404(Forum, slug=slug)
    context = {
        "slug": slug,
        "parent_model": parent_model,
        "parent_id": parent_id,
        "origin": origin,
        "target": target,
        "text": text,
    }
    if request.method == "POST":
        form = PostForm(request.POST, forum=forum)
        if form.is_valid():
            post = form.save()
            thread_initiator = post.get_thread_initiator()
            response = render(
                request,
                "forums/post_card.html",
                context={"forum": forum, "post": thread_initiator},
            )
            # if the parent is a forum, then this is a new Motion or Post,
            # and we keep the requested target and swap (== beforebegin).
            # Otherwise, we retarget to the initiator post, and swap outerHTML.
            # In both cases we refocus the browser after settle.
            if parent_model in ["forum", "meeting"]:
                # trigger new post form closure
                response["HX-Trigger"] = f"newPost-{target}"
                # refocus browser on new post
                response["HX-Trigger-After-Settle"] = json.dumps(
                    {"newPost": f"{target}",}
                )
            else:
                # force rerendering of whole thread from initiator down
                response["HX-Retarget"] = f"#thread-{thread_initiator.id}"
                response["HX-Reswap"] = "outerHTML"
                # refocus browser on initiator
                response["HX-Trigger-After-Settle"] = json.dumps(
                    {"newPost": f"thread-{thread_initiator.id}",}
                )
            print(f'{response["HX-Trigger-After-Settle"] = }')
            return response
    else:
        subject = ""
        if parent_model == "forum":
            parent_content_type = ContentType.objects.get(
                app_label="forums",
                model="forum",
            )
        elif parent_model in ["post", "motion"]:
            parent_content_type = ContentType.objects.get(
                app_label="forums",
                model=parent_model,
            )
            parent = parent_content_type.get_object_for_this_type(pk=parent_id)
            if parent.subject.startswith("Re: ..."):
                subject = parent.subject
            elif parent.subject.startswith("Re:"):
                subject = "%s%s" % ("Re: ...", parent.subject.lstrip("Re:"))
            else:
                subject = "Re: %s" % parent.subject
        else:
            raise Http404
        initial = {
            "posted_by": request.user,
            "posted_on": timezone.now(),
            "parent_content_type": parent_content_type,
            "parent_object_id": parent_id,
            "subject": subject,
        }
        form = PostForm(initial=initial, forum=forum)
    context["form"] = form
    return render(request, "forums/_hx_post_form.html", context)


@permission_required_or_403("forums.can_post_to_forum", (Forum, "slug", "slug"))
def _hx_motion_form_button(request, slug):
    context = { "slug": slug, }
    return render(request, "forums/_hx_motion_form_button.html", context)


@permission_required_or_403("forums.can_post_to_forum", (Forum, "slug", "slug"))
def _hx_motion_form(request, slug):
    forum = get_object_or_404(Forum, slug=slug)
    if request.method == "POST":
        form = MotionForm(request.POST, forum=forum)
        if form.is_valid():
            motion = form.save()
            response = render(
                request,
                "forums/post_card.html",
                context={"forum": forum, "post": motion.post},
            )
            # trigger new motion form closure
            response["HX-Trigger"] = f"newMotion"
            # refocus browser on new Motion
            response["HX-Trigger-After-Settle"] = json.dumps(
                {"newPost": f"thread-{motion.post.id}",}
            )
            return response
    else:
        parent_content_type = ContentType.objects.get(
            app_label="forums",
            model="forum",
        )
        voters = get_users_with_perms(forum)
        ineligible_ids = []
        for voter in voters.all():
            if not voter.has_perm("can_post_to_forum", forum):
                ineligible_ids.append(voter.id)
        initial = {
            "posted_by": request.user,
            "posted_on": timezone.now(),
            "parent_content_type": parent_content_type,
            "parent_object_id": forum.id,
            "eligible_for_voting": voters.exclude(id__in=ineligible_ids),
        }
        initial["voting_deadline"] = (
            (forum.meeting.date_until if forum.meeting else timezone.now()) +
            datetime.timedelta(days=7)
        )
        form = MotionForm(initial=initial, forum=forum)
    context = { "slug": slug, "form": form, }
    return render(request, "forums/_hx_motion_form.html", context)


@permission_required_or_403("forums.can_view_forum", (Forum, "slug", "slug"))
def _hx_thread_from_post(request, slug, post_id):
    forum = get_object_or_404(Forum, slug=slug)
    post = Post.objects.filter(pk=post_id).select_related(
        "motion",
        "posted_by",
    ).prefetch_related(
        "parent",
        "followup_posts",
    ).first()
    context = {
        "forum": forum,
        "post": post,
    }
    return render(request, "forums/post_card.html", context)



@permission_required_or_403("forums.can_post_to_forum", (Forum, "slug", "slug"))
def _hx_motion_voting(request, slug, motion_id):
    forum = get_object_or_404(Forum, slug=slug)
    motion = get_object_or_404(Motion.objects.prefetch_related(
        "eligible_for_voting__contributor__user",
        "in_agreement__contributor__user",
        "in_doubt__contributor__user",
        "in_disagreement__contributor__user",
        "in_abstain__contributor__user",
    ), pk=motion_id)
    initial = {
        "user": request.user.id,
        "motion": motion.id,
    }
    form = MotionVoteForm(request.POST or None, initial=initial)
    if form.is_valid():
        form.save()
        motion.refresh_from_db()
    context = { "forum": forum, "motion": motion, "form": form }
    return render(request, "forums/_hx_motion_voting.html", context)