# 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 that try exploiting the system via its public API."""

from typing import Any, ClassVar

from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient

from debusine.artifacts.models import TaskTypes
from debusine.assets import KeyPurpose as ArtifactKeyPurpose
from debusine.assets import SigningKeyData
from debusine.db.models import (
    Asset,
    AssetUsage,
    WorkRequest,
    WorkflowTemplate,
    Workspace,
)
from debusine.db.playground import scenarios
from debusine.server.celery import run_server_task
from debusine.server.workflows.base import orchestrate_workflow
from debusine.signing.tasks.models import SignData, SignDynamicData
from debusine.tasks.models import LookupMultiple
from debusine.test.django import TestCase, TestResponseType


class PenetrationTests(TestCase):
    """Try to game the system using its API."""

    scenario = scenarios.DefaultContextAPI()

    create_experiment_workspace: ClassVar[WorkflowTemplate]
    signing_key: ClassVar[Asset]
    signing_key_usage: ClassVar[AssetUsage]

    @classmethod
    def setUpTestData(cls) -> None:
        """Set up common test data."""
        super().setUpTestData()
        # Allow creation of experiment workspaces
        cls.create_experiment_workspace = (
            cls.playground.create_workflow_template(
                "create_experiment", "create_experiment_workspace"
            )
        )
        cls.signing_key = cls.playground.create_signing_key_asset()
        cls.signing_key_usage = cls.playground.create_asset_usage(
            cls.signing_key
        )

    def setUp(self) -> None:
        """Set up common objects."""
        super().setUp()
        self.client = APIClient()
        self.token = self.scenario.user_token

    def _post(
        self,
        endpoint: str,
        *,
        data: dict[str, Any],
        endpoint_kwargs: dict[str, Any] | None = None,
        scope: str,
    ) -> TestResponseType:
        headers = {"Token": self.token.key}
        headers["X-Debusine-Scope"] = scope

        return self.client.post(
            reverse(endpoint, kwargs=endpoint_kwargs),
            data=data,
            headers=headers,
            format="json",
        )

    def post_workflow(self, data: dict[str, Any]) -> TestResponseType:
        """Post a workflow creation request to api:workflows."""
        return self._post(
            "api:workflows", data=data, scope=self.scenario.scope.name
        )

    def post_permission_check(
        self,
        *,
        asset_category: str,
        asset_slug: str,
        permission_name: str,
        data: dict[str, Any],
    ) -> TestResponseType:
        """Post an asset permission check request."""
        return self._post(
            "api:asset-permission-check",
            endpoint_kwargs={
                "asset_category": asset_category,
                "asset_slug": asset_slug,
                "permission_name": permission_name,
            },
            data=data,
            scope=self.scenario.scope.name,
        )

    def assertExperimentWorkspaceCreated(self, **kwargs: Any) -> Workspace:
        """Create an experiment workspace and return its name."""
        response = self.post_workflow(
            {
                "template_name": "create_experiment",
                "task_data": kwargs,
            }
        )
        result = self.assertAPIResponseOk(response, status.HTTP_201_CREATED)

        # Run the workflow
        workflow = WorkRequest.objects.get(pk=result["id"])
        workflow.mark_running()
        orchestrate_workflow(workflow)

        # Run the server task
        server_task = workflow.children.get()
        run_server_task.apply(args=(server_task.pk,))
        server_task.refresh_from_db()
        self.assertEqual(server_task.status, WorkRequest.Statuses.COMPLETED)
        self.assertEqual(server_task.result, WorkRequest.Results.SUCCESS)

        return Workspace.objects.get(
            scope=self.scenario.scope,
            name=f"{self.scenario.workspace.name}-test",
        )

    def test_use_signing_key_from_experiment(self) -> None:
        """Attempt to use a workflow signing key in an experiment workspace."""
        self.playground.create_group_role(
            self.scenario.workspace,
            Workspace.Roles.CONTRIBUTOR,
            users=[self.scenario.user],
        )
        new_workspace = self.assertExperimentWorkspaceCreated(
            experiment_name="test"
        )
        signing_input = self.playground.create_signing_input_artifact(
            workspace=new_workspace
        )
        signing_key_data = self.signing_key.data_model
        assert isinstance(signing_key_data, SigningKeyData)

        signing_task = self.playground.create_work_request(
            workspace=new_workspace,
            task_type=TaskTypes.SIGNING,
            task_name="sign",
            task_data=SignData(
                purpose=ArtifactKeyPurpose(signing_key_data.purpose),
                unsigned=LookupMultiple.parse_obj([signing_input.id]),
                key=signing_key_data.fingerprint,
            ),
            dynamic_task_data=SignDynamicData(
                unsigned_ids=[signing_input.id],
                unsigned_binary_package_names=[
                    signing_input.data["binary_package_name"]
                ],
            ).dict(),
            created_by=self.scenario.user,
        )
        response = self.post_permission_check(
            asset_category=self.signing_key.category,
            asset_slug=self.signing_key.slug,
            permission_name="sign_with",
            data={
                "artifact_id": signing_input.id,
                "work_request_id": signing_task.id,
                "workspace": new_workspace.name,
            },
        )
        result = self.assertAPIResponseOk(response)
        self.assertFalse(result["has_permission"])
