# Copyright 2024 The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""debusine collection views."""

from functools import cached_property
from typing import Any

from django.db.models import Count, Q
from django.http import Http404

import yaml

from debusine.db.models import Collection, CollectionItem, Workspace
from debusine.web.forms import CollectionSearchForm
from debusine.web.utils import ui_prototype
from debusine.web.views.base import (
    DetailViewBase,
    FormMixinBase,
    ListViewBase,
)
from debusine.web.views.mixins import PaginationMixin


class WorkspaceViewMixin:
    """Common functions for workspace-specific views."""

    @cached_property
    def workspace(self):
        """Workspace for this request."""
        if self.request.user.is_authenticated:
            workspaces = Workspace.objects.all()
        else:
            workspaces = Workspace.objects.filter(public=True)
        try:
            return workspaces.get(name=self.kwargs["wname"])
        except Workspace.DoesNotExist:
            raise Http404(f"Workspace {self.kwargs['wname']} not found")

    def get_context_data(self, *args, **kwargs):
        """Return context_data with workspace."""
        context = super().get_context_data(**kwargs)
        context["workspace"] = self.workspace
        return context


class CollectionListView(WorkspaceViewMixin, ListViewBase[Collection]):
    """List collections."""

    model = Collection
    template_name = "web/collection-list.html"
    context_object_name = "collection_list"
    ordering = ["category", "name"]

    def get_queryset(self):
        """Filter collection by accessible workspace."""
        queryset = super().get_queryset()
        return queryset.filter(workspace=self.workspace).exclude(
            category="debusine:workflow-internal"
        )


@ui_prototype
class CollectionCategoryListView(WorkspaceViewMixin, ListViewBase[Collection]):
    """List collections with a given category."""

    model = Collection
    template_name = "web/collection-category-list.html"
    context_object_name = "collection_list"
    ordering = ["name"]

    def get_queryset(self):
        """Filter collection by accessible workspace."""
        queryset = super().get_queryset()
        return (
            queryset.filter(workspace=self.workspace)
            .exclude(category="debusine:workflow-internal")
            .filter(category=self.kwargs["ccat"])
        )

    def get_context_data(self, *args, **kwargs):
        """Return context_data with category."""
        context = super().get_context_data(**kwargs)
        context["category"] = self.kwargs["ccat"]
        return context


class CollectionViewMixin(WorkspaceViewMixin):
    """Common functions for collection-specific views."""

    @cached_property
    def collection(self):
        """Workspace for this request."""
        try:
            return Collection.objects.get(
                workspace=self.workspace,
                name=self.kwargs["cname"],
                category=self.kwargs["ccat"],
            )
        except Collection.DoesNotExist:
            raise Http404(
                f"{self.kwargs['cname']}@{self.kwargs['ccat']}"
                " collection not found"
            )

    def get_context_data(self, *args, **kwargs):
        """Return context_data with collection."""
        context = super().get_context_data(**kwargs)
        context["collection"] = self.collection
        return context


class CollectionDetailView(CollectionViewMixin, DetailViewBase[Collection]):
    """Show a collection detail."""

    model = Collection
    template_name = "web/collection-detail.html"
    context_object_name = "collection"

    def get_object(self, queryset=None) -> Collection:  # noqa: U100
        """Return the collection object to show."""
        return self.collection

    @ui_prototype
    def get_context_data(self, *args, **kwargs):
        """Return context_data with work_request_list and workspace_list."""
        context = super().get_context_data(**kwargs)
        if self.object.data:
            context["data_yaml"] = yaml.safe_dump(self.object.data)

        context["collections"] = CollectionItem.active_objects.filter(
            parent_collection=self.object, collection__isnull=False
        ).order_by("category", "name")

        context["artifacts"] = (
            CollectionItem.objects.filter(
                parent_collection=self.object, artifact__isnull=False
            )
            .values("category")
            .annotate(
                count=Count("category", filter=Q(removed_at__isnull=True)),
                count_removed=Count(
                    "category", filter=Q(removed_at__isnull=False)
                ),
            )
            .order_by("category")
        )

        context["bare"] = CollectionItem.active_objects.filter(
            parent_collection=self.object,
            collection__isnull=True,
            artifact__isnull=True,
        ).order_by("category", "name")

        return context


class CollectionSearchView(
    CollectionViewMixin,
    FormMixinBase[CollectionSearchForm],
    PaginationMixin,
    ListViewBase[CollectionItem],
):
    """Search a collection contents."""

    model = CollectionItem
    template_name = "web/collection-search.html"
    context_object_name = "item_list"
    paginate_by = 50
    form_class = CollectionSearchForm

    @ui_prototype
    def get_queryset(self):
        """All items belonging to the current collection."""
        queryset = (
            super().get_queryset().filter(parent_collection=self.collection)
        )
        # FIXME: this builds the form twice: if it becomes a problem we can
        # override get_form to cache its result
        form = self.get_form()
        if form.is_valid():
            if name := form.cleaned_data["name"]:
                queryset = queryset.filter(name__startswith=name)
            if category := form.cleaned_data["category"]:
                queryset = queryset.filter(category=category)
            if form.cleaned_data["historical"]:
                queryset = queryset.filter(removed_at__isnull=False)
            else:
                queryset = queryset.filter(removed_at__isnull=True)
        return queryset

    @ui_prototype
    def get_form_kwargs(self) -> dict[str, Any]:
        """Get arguments used to instantiate the form."""
        kwargs = super().get_form_kwargs()
        kwargs["instance"] = self.collection
        kwargs["data"] = self.request.GET
        return kwargs

    @ui_prototype
    def get_ordering(self):
        """Return field used for sorting."""
        order = self.request.GET.get("order")
        if order in ("name", "category", "created_at"):
            if self.request.GET["asc"] == "0":
                return "-" + order
            else:
                return order
        return "category"

    @ui_prototype
    def get_context_data(self, *args, **kwargs):
        """Return context_data with work_request_list and workspace_list."""
        context = super().get_context_data(**kwargs)
        context["collection"] = self.collection
        context["order"] = self.get_ordering().removeprefix("-")
        context["asc"] = self.request.GET.get("asc", "0")
        return context


@ui_prototype
class CollectionItemDetailView(
    CollectionViewMixin, DetailViewBase[CollectionItem]
):
    """Show a collection item detail."""

    model = CollectionItem
    template_name = "web/collection-item-detail.html"
    context_object_name = "item"

    def get_object(self, queryset=None) -> CollectionItem:
        """Return the collection object to show."""
        if queryset is None:
            queryset = self.get_queryset()  # pragma: no cover
        try:
            return queryset.filter(removed_at__isnull=True).get(
                pk=self.kwargs["iid"], parent_collection=self.collection
            )
        except CollectionItem.DoesNotExist:
            raise Http404(
                f"{self.kwargs['iid']} ({self.kwargs['iname']})"
                " item not found in"
                f"{self.kwargs['cname']}@{self.kwargs['ccat']}"
            )

    @ui_prototype
    def get_context_data(self, *args, **kwargs):
        """Return context_data."""
        context = super().get_context_data(**kwargs)
        if self.object.data:
            context["data_yaml"] = yaml.safe_dump(self.object.data)
        return context
