# 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 the scopes UI helper."""

import abc
from typing import override

from django.core.exceptions import ImproperlyConfigured
from django.test import RequestFactory
from django.urls import reverse

from debusine.db.context import context
from debusine.db.permissioncontext import PermissionContext
from debusine.db.playground import scenarios
from debusine.test.django import TestCase
from debusine.web.icons import Icons
from debusine.web.views.places import PlaceValue
from debusine.web.views.tests.utils import ViewTestMixin
from debusine.web.views.ui.scopes import (
    ScopeMainPlace,
    ScopePlace,
    ScopeSubPlace,
    ScopeUI,
)


class ScopeUITests(ViewTestMixin, TestCase):
    """Test the :py:class:`ScopeUI` class."""

    scenario = scenarios.DefaultContext(set_current=True)

    def ui(self) -> ScopeUI:
        """Instantiate the UI helper to test."""
        assert context.user
        factory = RequestFactory()
        request = factory.get("/")
        request.user = context.user
        return self.scenario.scope.ui(request)

    def test_place(self) -> None:
        place = self.ui().place
        self.assertIsInstance(place, ScopeMainPlace)
        self.assertPlaceValueEqual(place.title, self.scenario.scope.name)
        self.assertPlaceValueEqual(place.description, "debusine scope")
        self.assertEqual(place.url, reverse("scopes:detail"))
        self.assertPlaceValueEqual(place.icon, Icons.SCOPE)
        self.assertIsNone(place.parent)

    def test_place_with_custom_icon(self) -> None:
        self.scenario.scope.icon = "icon.png"
        self.scenario.scope.save()
        place = self.ui().place
        self.assertIsInstance(place, ScopeMainPlace)
        self.assertPlaceValueEqual(place.title, self.scenario.scope.name)
        self.assertPlaceValueEqual(place.description, "debusine scope")
        self.assertEqual(place.url, reverse("scopes:detail"))
        self.assertPlaceValueEqual(place.icon, Icons.SCOPE)
        self.assertIsNone(place.parent)

    def test_place_group_list(self) -> None:
        place = self.ui().place_group_list
        self.assertIsInstance(place, ScopeSubPlace)
        self.assertPlaceValueEqual(
            place.title,
            "groups",
            page="Group list for playground in scope debusine",
        )
        self.assertPlaceValueEqual(
            place.description,
            f"Groups in scope debusine accessible by {self.scenario.user}",
        )
        self.assertEqual(
            place.url, reverse("user:groups", kwargs={"username": "playground"})
        )
        self.assertIsNone(place.icon)
        self.assertEqual(place.title.get("breadcrumb"), "groups")
        assert place.parent is not None
        self.assertEqual(place.parent.url, reverse("scopes:detail"))

    @context.local()
    def test_place_group_list_unauthenticated(self) -> None:
        context.reset()
        context.set_permission_context_scoped(
            self.scenario.scope, PermissionContext.for_ui_anonymous()
        )
        with self.assertRaisesRegex(
            ImproperlyConfigured,
            "ScopeUI.place_group_list invoked by an anonymous user",
        ):
            self.ui().place_group_list


class ScopePlaceTests(TestCase, abc.ABC):
    """Test the :py:class:`ScopePlace` class."""

    scenario = scenarios.DefaultScopeUser()

    scope_class: type[ScopePlace]

    @override
    def setUp(self) -> None:
        super().setUp()
        request = RequestFactory().get("/")
        self.scope_ui = self.scenario.scope.ui(request)

    def make_place(self, icon: str | None = None) -> ScopePlace:
        return self.scope_class(
            ui=self.scope_ui,
            title=PlaceValue("test title", breadcrumb="test"),
            url="/test-url",
            description="description",
            icon=icon,
        )

    def test_as_icon_no_icon(self) -> None:
        place = self.make_place()
        self.assertEqual(place.as_icon(), "")

    def test_as_icon_no_icon_scope_with_custom_icon(self) -> None:
        self.scenario.scope.icon = "icon.png"
        self.scenario.scope.save()
        place = self.make_place()
        self.assertEqual(place.as_icon(), "")

    def test_as_icon_default_icon(self) -> None:
        place = self.make_place(icon=Icons.SCOPE)
        tree = self.assertHTMLValid(place.as_icon())
        span = self.assertHasElement(tree, "//span")
        self.assertEqual(span.get("class"), f"bi bi-{Icons.SCOPE}")

    def test_as_icon_custom_icon(self) -> None:
        self.scenario.scope.icon = "icon.png"
        self.scenario.scope.save()
        place = self.make_place(icon=Icons.SCOPE)
        tree = self.assertHTMLValid(place.as_icon())
        img = self.assertHasElement(tree, "//img")
        self.assertEqual(img.get("src"), "/static/icon.png")

    def test_as_nav_link(self) -> None:
        place = self.make_place()
        tree = self.assertHTMLValid(place.as_nav_link())
        a = self.assertHasElement(tree, "//a")
        self.assertTextContentEqual(a, "test title")
        self.assertEqual(a.get("href"), "/test-url")

    def test_as_nav_link_icon(self) -> None:
        self.scenario.scope.icon = "icon.png"
        place = self.make_place(icon=Icons.SCOPE)
        tree = self.assertHTMLValid(place.as_nav_link())
        a = self.assertHasElement(tree, "//a")
        self.assertTextContentEqual(a, "test title")
        self.assertEqual(a.get("href"), "/test-url")

        img = self.assertHasElement(a, "img")
        self.assertEqual(img.get("src"), "/static/icon.png")


class ScopeMainPlaceTests(ScopePlaceTests):
    """Tests for :py:class:`ScopeMainPlace`."""

    scope_class = ScopeMainPlace

    def test_as_page_title(self) -> None:
        self.scenario.scope.icon = "icon.png"
        place = self.make_place(icon=Icons.SCOPE)
        tree = self.assertHTMLValid(place.as_page_title())
        h1 = self.assertHasElement(tree, "//h1")
        self.assertTextContentEqual(h1, "test title")


class ScopeSubPlaceTests(ScopePlaceTests):
    """Tests for :py:class:`ScopeSubPlace`."""

    scope_class = ScopeSubPlace

    def test_as_page_title(self) -> None:
        self.scenario.scope.icon = "icon.png"
        place = self.make_place(icon=Icons.SCOPE)
        self.assertEqual(place.as_page_title(), "")


del ScopePlaceTests
