# Copyright © 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.

"""Tests for the collections views."""

from typing import ClassVar
from unittest import mock

from django.urls import reverse
from rest_framework import status

from debusine.artifacts.models import BareDataCategory, CollectionCategory
from debusine.db.context import context
from debusine.db.models import Collection, CollectionItem, Workspace
from debusine.test.django import TestCase
from debusine.web.templatetags.debusine import (
    ui_shortcuts as template_ui_shortcuts,
)
from debusine.web.views import ui_shortcuts
from debusine.web.views.tests.utils import ViewTestMixin


class CollectionViewsTests(ViewTestMixin, TestCase):
    """Tests for Collection views."""

    workspace_public: ClassVar[Workspace]
    workspace_private: ClassVar[Workspace]
    collection_public: ClassVar[Collection]
    collection_private: ClassVar[Collection]
    collection_public_items: ClassVar[list[CollectionItem]]

    @classmethod
    @context.disable_permission_checks()
    def setUpTestData(cls) -> None:
        """Set up a database layout for views."""
        super().setUpTestData()
        cls.workspace_public = cls.playground.create_workspace(
            name="Public", public=True
        )
        cls.workspace_private = cls.playground.create_workspace(name="Private")
        cls.collection_public = cls.playground.create_collection(
            "public",
            CollectionCategory.WORKFLOW_INTERNAL,
            workspace=cls.workspace_public,
        )
        cls.collection_private = cls.playground.create_collection(
            "private",
            CollectionCategory.WORKFLOW_INTERNAL,
            workspace=cls.workspace_private,
        )
        manager = cls.collection_public.manager
        cls.collection_public_items = []
        for i in range(1, 11):
            cls.collection_public_items.append(
                manager.add_bare_data(
                    BareDataCategory.TEST,
                    user=cls.playground.get_default_user(),
                    name=f"test{i}",
                    data={"idx": i},
                )
            )

    def test_list_permissions(self) -> None:
        """Test permissions on the collection list view."""
        response = self.client.get(
            reverse("workspaces:collections:list", kwargs={"wname": "Public"})
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        response = self.client.get(
            reverse("workspaces:collections:list", kwargs={"wname": "Private"})
        )
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

        response = self.client.get(
            reverse(
                "workspaces:collections:list",
                kwargs={"wname": "does-not-exist"},
            )
        )
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

        self.client.force_login(self.playground.get_default_user())
        response = self.client.get(
            reverse("workspaces:collections:list", kwargs={"wname": "Private"})
        )
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
        self.playground.create_group_role(
            self.workspace_private,
            Workspace.Roles.OWNER,
            users=[self.playground.get_default_user()],
        )
        response = self.client.get(
            reverse("workspaces:collections:list", kwargs={"wname": "Private"})
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_list(self) -> None:
        """Test collection list view."""
        response = self.client.get(
            reverse("workspaces:collections:list", kwargs={"wname": "Public"})
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertHTMLValid(response)
        # TODO: add more functional view tests here once the actual UI
        # interaction gets validated

    def assertCollectionDetailViewPermissions(self, url_name: str) -> None:
        """Test permission on views that show a collection."""
        response = self.client.get(
            reverse(
                url_name,
                kwargs={
                    "wname": "Public",
                    "ccat": CollectionCategory.WORKFLOW_INTERNAL,
                    "cname": "public",
                },
            )
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        response = self.client.get(
            reverse(
                url_name,
                kwargs={
                    "wname": "Private",
                    "ccat": CollectionCategory.WORKFLOW_INTERNAL,
                    "cname": "private",
                },
            )
        )
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

        response = self.client.get(
            reverse(
                url_name,
                kwargs={
                    "wname": "does-not-exist",
                    "ccat": CollectionCategory.WORKFLOW_INTERNAL,
                    "cname": "public",
                },
            )
        )
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

        response = self.client.get(
            reverse(
                url_name,
                kwargs={
                    "wname": "Public",
                    "ccat": CollectionCategory.WORKFLOW_INTERNAL,
                    "cname": "does-not-exist",
                },
            )
        )
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

        response = self.client.get(
            reverse(
                url_name,
                kwargs={
                    "wname": "Public",
                    "ccat": "debusine:does-not-exist",
                    "cname": "public",
                },
            )
        )
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

        self.client.force_login(self.playground.get_default_user())
        response = self.client.get(
            reverse(
                url_name,
                kwargs={
                    "wname": "Private",
                    "ccat": CollectionCategory.WORKFLOW_INTERNAL,
                    "cname": "private",
                },
            )
        )
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

        self.playground.create_group_role(
            self.workspace_private,
            Workspace.Roles.OWNER,
            users=[self.playground.get_default_user()],
        )
        response = self.client.get(
            reverse(
                url_name,
                kwargs={
                    "wname": "Private",
                    "ccat": CollectionCategory.WORKFLOW_INTERNAL,
                    "cname": "private",
                },
            )
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_detail_permissions(self) -> None:
        """Test permissions on the collection details view."""
        self.assertCollectionDetailViewPermissions(
            "workspaces:collections:detail"
        )

    def test_detail(self) -> None:
        """Test collection detail view."""
        response = self.client.get(
            reverse(
                "workspaces:collections:detail",
                kwargs={
                    "wname": "Public",
                    "ccat": CollectionCategory.WORKFLOW_INTERNAL,
                    "cname": "public",
                },
            )
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertHTMLValid(response)

    def test_search_permissions(self) -> None:
        """Test permissions on the collection search view."""
        self.assertCollectionDetailViewPermissions(
            "workspaces:collections:search"
        )

    def test_search_defaults(self) -> None:
        """Test collection search view."""
        response = self.client.get(
            reverse(
                "workspaces:collections:search",
                kwargs={
                    "wname": "Public",
                    "ccat": CollectionCategory.WORKFLOW_INTERNAL,
                    "cname": "public",
                },
            )
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertHTMLValid(response)

        ctx = response.context

        self.assertCountEqual(ctx["items"], self.collection_public_items)

        self.assertEqual(ctx["collection"], self.collection_public)
        self.assertEqual(ctx["order"], "category")
        self.assertEqual(ctx["asc"], "0")

        sample_item = ctx["items"][0]
        self.assertEqual(
            template_ui_shortcuts(sample_item),
            [ui_shortcuts.create_collection_item(sample_item)],
        )

        page = ctx["page_obj"]
        self.assertEqual(page.number, 1)

        paginator = page.paginator
        self.assertEqual(paginator.per_page, 50)
        self.assertEqual(paginator.count, 10)
        self.assertEqual(paginator.num_pages, 1)

    def test_search_pagination(self) -> None:
        """Test collection search view pagination."""
        with mock.patch(
            "debusine.web.views.collections"
            ".CollectionSearchView.get_paginate_by",
            return_value=3,
        ):
            response = self.client.get(
                reverse(
                    "workspaces:collections:search",
                    kwargs={
                        "wname": "Public",
                        "ccat": CollectionCategory.WORKFLOW_INTERNAL,
                        "cname": "public",
                    },
                )
            )
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertHTMLValid(response)

        ctx = response.context

        self.assertEqual(len(ctx["items"]), 3)

        page = ctx["page_obj"]
        self.assertEqual(page.number, 1)

        paginator = page.paginator
        self.assertEqual(paginator.per_page, 3)
        self.assertEqual(paginator.count, 10)
        self.assertEqual(paginator.num_pages, 4)

    def test_search_query_count(self) -> None:
        """Test that collection search makes a reasonable amount of queries."""
        with self.assertNumQueries(13):
            self.client.get(
                reverse(
                    "workspaces:collections:search",
                    kwargs={
                        "wname": "Public",
                        "ccat": CollectionCategory.WORKFLOW_INTERNAL,
                        "cname": "public",
                    },
                )
            )

    # TODO: add more functional view tests here once the actual UI
    # interaction gets validated
