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

"""Unit tests for the task to create child workspaces."""

import datetime as dt
from typing import ClassVar, override

from django.utils import timezone

from debusine.artifacts.models import (
    SINGLETON_COLLECTION_CATEGORIES,
    WorkRequestResults,
)
from debusine.db.models import Group, WorkflowTemplate, Workspace
from debusine.db.playground import scenarios
from debusine.server.tasks import (
    CreateChildWorkspace,
    ServerTaskPermissionDenied,
)
from debusine.server.tasks.create_child_workspace import CannotCreate
from debusine.server.tasks.models import CreateChildWorkspaceData
from debusine.server.tasks.tests.helpers import ServerTaskTestCase
from debusine.test.django import AllowAll, DenyAll, override_permission


class CreateChildWorkspaceTests(ServerTaskTestCase[CreateChildWorkspace]):
    """Tests for :py:class:`CreateChildWorkspace`."""

    SAMPLE_TASK_DATA = {"prefix": "System", "suffix": "test"}

    scenario = scenarios.DefaultContext()
    workflow_template_1: ClassVar[WorkflowTemplate]
    workflow_template_2: ClassVar[WorkflowTemplate]

    @override
    @classmethod
    def setUpTestData(cls) -> None:
        super().setUpTestData()
        cls.workflow_template_1 = cls.playground.create_workflow_template(
            name="workflow_template_1", task_name="noop"
        )
        cls.workflow_template_2 = cls.playground.create_workflow_template(
            name="workflow_template_2", task_name="noop"
        )

    def test_compute_scheduler_tags(self) -> None:
        self.assertComputesSchedulerTags()

    def test_get_label(self) -> None:
        """Test get_label."""
        self.assertEqual(
            self.task.get_label(), "create child workspace 'System-test'"
        )

    def test_validate_prefix(self) -> None:
        """Test prefix validation."""
        for name in ["test-test"]:
            with self.subTest(name=name):
                CreateChildWorkspaceData(prefix=name, suffix="suffix")

        for name in ["0test", "test@test", ".test", "+test"]:
            with (
                self.subTest(name=name),
                self.assertRaisesRegex(
                    ValueError, r"\bprefix\n.*String should match pattern"
                ),
            ):
                CreateChildWorkspaceData(prefix=name, suffix="suffix")

    def test_validate_suffix(self) -> None:
        """Test suffix validation."""
        for name in ["test-test"]:
            with self.subTest(name=name):
                CreateChildWorkspaceData(prefix="prefix", suffix=name)

        for name in ["0test", "test@test", ".test", "+test"]:
            with (
                self.subTest(name=name),
                self.assertRaisesRegex(
                    ValueError, r"\bsuffix\n.*String should match pattern"
                ),
            ):
                CreateChildWorkspaceData(prefix="prefix", suffix=name)

    @override_permission(Workspace, "can_create_child_workspace", DenyAll)
    def test_execute_no_perms(self) -> None:
        """Test running the task with insufficient permissions."""
        with self.assertRaisesRegex(
            ServerTaskPermissionDenied,
            "playground cannot create a child workspace from debusine/System",
        ):
            self.task.execute()

    @override_permission(Workspace, "can_create_child_workspace", AllowAll)
    def test_execute_defaults(self) -> None:
        """Test running the task with default arguments."""
        test_start = timezone.now()
        self.assertEqual(self.task.execute(), WorkRequestResults.SUCCESS)

        new_workspace = Workspace.objects.get(
            scope=self.scenario.scope, name="System-test"
        )
        self.assertTrue(new_workspace.public)
        self.assertEqual(
            new_workspace.default_expiration_delay, dt.timedelta(0)
        )
        self.assertQuerySetEqual(
            new_workspace.inherits.all(), [self.scenario.workspace]
        )
        self.assertGreaterEqual(new_workspace.created_at, test_start)
        self.assertEqual(new_workspace.expiration_delay, dt.timedelta(days=60))

        self.assertQuerySetEqual(
            new_workspace.collections.values_list("category", flat=True),
            SINGLETON_COLLECTION_CATEGORIES,
            ordered=False,
        )

        group = Group.objects.get(
            scope=self.scenario.scope, workspace=new_workspace, name="Owners"
        )
        self.assertTrue(group.ephemeral)
        self.assertQuerySetEqual(group.users.all(), [self.scenario.user])
        # TODO: assert that user is admin of the group (after !1611)

        role = new_workspace.roles.get(group=group)
        self.assertEqual(role.role, Workspace.Roles.OWNER)

        self.assertQuerySetEqual(
            WorkflowTemplate.objects.filter(workspace=new_workspace), []
        )

    @override_permission(Workspace, "can_create_child_workspace", AllowAll)
    def test_execute_all_set(self) -> None:
        """Test running the task with all arguments set."""
        test_start = timezone.now()
        self.task_data["public"] = False
        self.task_data["owner_group"] = self.scenario.workspace_owners.name
        self.task_data["workflow_template_names"] = ["workflow_template_1"]
        self.task_data["expiration_delay"] = 7
        self.assertEqual(self.task.execute(), WorkRequestResults.SUCCESS)

        new_workspace = Workspace.objects.get(
            scope=self.scenario.scope, name="System-test"
        )
        self.assertFalse(new_workspace.public)
        self.assertEqual(
            new_workspace.default_expiration_delay, dt.timedelta(0)
        )
        self.assertQuerySetEqual(
            new_workspace.inherits.all(), [self.scenario.workspace]
        )
        self.assertGreaterEqual(new_workspace.created_at, test_start)
        self.assertEqual(new_workspace.expiration_delay, dt.timedelta(days=7))

        role = new_workspace.roles.get(group=self.scenario.workspace_owners)
        self.assertEqual(role.role, Workspace.Roles.OWNER)

        new_workflow_templates = list(
            WorkflowTemplate.objects.filter(workspace=new_workspace)
        )
        self.assertEqual(len(new_workflow_templates), 1)

        self.assertEqual(
            new_workflow_templates[0].name, self.workflow_template_1.name
        )
        self.assertEqual(new_workflow_templates[0].workspace, new_workspace)
        self.assertEqual(
            new_workflow_templates[0].task_name,
            self.workflow_template_1.task_name,
        )
        self.assertEqual(
            new_workflow_templates[0].static_parameters,
            self.workflow_template_1.static_parameters,
        )
        self.assertEqual(
            new_workflow_templates[0].priority,
            self.workflow_template_1.priority,
        )

    @override_permission(Workspace, "can_create_child_workspace", AllowAll)
    def test_wrong_group_name(self) -> None:
        """Test running the task with a misspelled group name."""
        self.task_data["owner_group"] = "misspelled-name"
        with self.assertRaisesRegex(
            CannotCreate, "owner group misspelled-name not found"
        ):
            self.task.execute()

    @override_permission(Workspace, "can_create_child_workspace", AllowAll)
    def test_wrong_workflowtemplate_name(self) -> None:
        """Test running the task with a misspelled workflow template name."""
        self.task_data["workflow_template_names"] = ["misspelled-name"]
        with self.assertRaisesRegex(
            CannotCreate, "System/misspelled-name does not exist"
        ):
            self.task.execute()
