#
# Copyright 2023 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# You can find the source code of the website at https://github.com/ae-utbm/sith
#
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#

from django.contrib.auth.mixins import PermissionRequiredMixin, UserPassesTestMixin
from django.db.models import F, OuterRef, Subquery
from django.db.models.functions import Coalesce
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy
from django.utils.functional import cached_property
from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView

from core.auth.mixins import CanEditPropMixin, CanViewMixin
from core.models import Page, PageRev
from core.views.forms import PageForm, PagePropForm, PageRevisionForm


class PageNotFound(Http404):
    """Http404 Exception, but specifically for when the not found object is a Page."""

    def __init__(self, page_name: str):
        self.page_name = page_name


def get_page_or_404(full_name: str) -> Page:
    """Like Django's get_object_or_404, but for Page, and with a custom 404 exception."""
    page = Page.objects.filter(_full_name=full_name).first()
    if not page:
        raise PageNotFound(full_name)
    return page


class PageListView(ListView):
    model = Page
    template_name = "core/page/list.jinja"

    def get_queryset(self):
        return (
            Page.objects.viewable_by(self.request.user)
            .annotate(
                display_name=Coalesce(
                    Subquery(
                        PageRev.objects.filter(page=OuterRef("id"))
                        .order_by("-date")
                        .values("title")[:1]
                    ),
                    F("name"),
                )
            )
            .select_related("parent")
        )


class BasePageDetailView(CanViewMixin, DetailView):
    model = Page
    slug_url_kwarg = "page_name"
    _cached_object: Page | None = None

    def dispatch(self, request, *args, **kwargs):
        page = self.get_object()
        if page.need_club_redirection:
            return redirect("club:club_view", club_id=page.club.id)
        return super().dispatch(request, *args, **kwargs)

    def get_object(self, *args, **kwargs):
        if not self._cached_object:
            full_name = self.kwargs.get(self.slug_url_kwarg)
            self._cached_object = get_page_or_404(full_name)
        return self._cached_object

    def get_context_data(self, **kwargs):
        return super().get_context_data(**kwargs) | {
            "last_revision": self.object.revisions.last()
        }


class PageView(BasePageDetailView):
    template_name = "core/page/detail.jinja"


class PageHistView(BasePageDetailView):
    template_name = "core/page/history.jinja"


class PageRevView(BasePageDetailView):
    template_name = "core/page/detail.jinja"

    def dispatch(self, request, *args, **kwargs):
        page = self.get_object()
        if page.need_club_redirection:
            return redirect(
                "club:club_view_rev", club_id=page.club.id, rev_id=kwargs["rev"]
            )
        self.revision = get_object_or_404(page.revisions, id=self.kwargs["rev"])
        return super().dispatch(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        return super().get_context_data(**kwargs) | {"revision": self.revision}


class PageCreateView(PermissionRequiredMixin, CreateView):
    model = Page
    form_class = PageForm
    template_name = "core/create.jinja"
    permission_required = "core.add_page"

    def get_initial(self):
        init = super().get_initial()
        if "page" not in self.request.GET:
            return init
        page_name = self.request.GET["page"].rsplit("/", maxsplit=1)
        if len(page_name) == 2:
            parent = Page.get_page_by_full_name(page_name[0])
            if parent is not None:
                init["parent"] = parent.id
        init["name"] = page_name[-1]
        return init

    def form_valid(self, form):
        form.instance.set_lock(self.request.user)
        ret = super().form_valid(form)
        return ret


class CanEditPagePropMixin(CanEditPropMixin):
    def dispatch(self, request, *args, **kwargs):
        res = super().dispatch(request, *args, **kwargs)
        if self.object.is_club_page:
            raise Http404
        return res


class PagePropView(CanEditPagePropMixin, UpdateView):
    model = Page
    form_class = PagePropForm
    template_name = "core/page/prop.jinja"

    def get_object(self, queryset=None):
        self.page = get_page_or_404(full_name=self.kwargs["page_name"])
        self.page.set_lock_recursive(self.request.user)
        return self.page


class BasePageEditView(UserPassesTestMixin, UpdateView):
    model = PageRev
    form_class = PageRevisionForm
    template_name = "core/page/edit.jinja"

    def test_func(self):
        return self.request.user.can_edit(self.page)

    @cached_property
    def page(self) -> Page:
        page = get_page_or_404(full_name=self.kwargs["page_name"])
        page.set_lock(self.request.user)
        return page

    def get_object(self, *args, **kwargs):
        return self.page.revisions.last()

    def get_context_data(self, **kwargs):
        return super().get_context_data(**kwargs) | {"page": self.page}

    def get_form_kwargs(self):
        return super().get_form_kwargs() | {
            "author": self.request.user,
            "page": self.page,
        }


class PageEditView(BasePageEditView):
    def dispatch(self, request, *args, **kwargs):
        if self.page.need_club_redirection:
            return redirect("club:club_edit_page", club_id=self.page.club.id)
        return super().dispatch(request, *args, **kwargs)


class PageDeleteView(CanEditPagePropMixin, DeleteView):
    model = Page
    template_name = "core/delete_confirm.jinja"
    pk_url_kwarg = "page_id"
    success_url = reverse_lazy("core:page_list")
