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

"""Unit tests for the qa workflow."""
from collections.abc import Sequence
from typing import Any

from debusine.artifacts.models import ArtifactCategory
from debusine.db.models import Artifact, WorkRequest
from debusine.server.workflows import QAWorkflow
from debusine.server.workflows.base import Workflow, orchestrate_workflow
from debusine.server.workflows.models import BaseWorkflowData
from debusine.tasks.models import BackendType, BaseDynamicTaskData, TaskTypes
from debusine.tasks.tests.helper_mixin import TestTaskMixin
from debusine.test.django import TestCase
from debusine.test.utils import preserve_task_registry


class QAWorkflowTests(TestCase):
    """Unit tests for :py:class:`QAWorkflow`."""

    def create_qa_workflow(
        self, *, extra_task_data: dict[str, Any]
    ) -> QAWorkflow:
        """Create a qa workflow."""
        task_data = {
            "source_artifact": "2@artifacts",
            "binary_artifacts": ["3@artifacts"],
            "vendor": "debian",
            "codename": "bookworm",
        }
        task_data.update(extra_task_data)
        wr = self.playground.create_workflow(
            task_name="qa",
            task_data=task_data,
        )
        return QAWorkflow(wr)

    def orchestrate(
        self,
        extra_data: dict[str, Any],
        *,
        architectures: Sequence[str] | None = None,
    ) -> WorkRequest:
        """Create a QAWorkflow and call orchestrate_workflow."""

        class ExamplePipeline(
            TestTaskMixin, Workflow[BaseWorkflowData, BaseDynamicTaskData]
        ):
            """Example workflow."""

            def populate(self) -> None:
                """Populate the pipeline."""
                sbuild = self.work_request.create_child(
                    task_type=TaskTypes.WORKFLOW, task_name="sbuild"
                )

                if architectures is not None:
                    for arch in architectures:
                        child = sbuild.create_child(
                            task_name="sbuild",
                            task_data={"host_architecture": arch},
                        )

                        self.provides_artifact(
                            child,
                            ArtifactCategory.BINARY_PACKAGE,
                            f"build-{arch}",
                            data={"architecture": arch},
                        )

                data = {
                    "source_artifact": "20@artifacts",
                    "binary_artifacts": ["30@artifacts", "31@artifacts"],
                    "vendor": "debian",
                    "codename": "bookworm",
                }

                qa = self.work_request.create_child(
                    task_type=TaskTypes.WORKFLOW,
                    task_name="qa",
                    task_data={**data, **extra_data},
                )
                QAWorkflow(qa).populate()

        root = self.playground.create_workflow(task_name="examplepipeline")

        root.mark_running()
        orchestrate_workflow(root)

        return root.children.get(task_name="qa")

    def create_binary_packages(
        self,
        srcpkg_name: str,
        srcpkg_version: str,
        version: str,
        architecture: str,
    ) -> Artifact:
        """Create a minimal `debian:binary-packages` artifact."""
        artifact, _ = self.playground.create_artifact(
            category=ArtifactCategory.BINARY_PACKAGES,
            data={
                "srcpkg_name": srcpkg_name,
                "srcpkg_version": srcpkg_version,
                "version": version,
                "architecture": architecture,
                "packages": [],
            },
        )
        return artifact

    @preserve_task_registry()
    def test_populate(self):
        """Test populate."""
        architectures = ["amd64"]
        source_artifact = self.playground.create_source_artifact(name="hello")

        workflow = self.orchestrate(
            extra_data={
                "source_artifact": f"{source_artifact.id}@artifacts",
                "binary_artifacts": ["internal@collections/name:build-amd64"],
            },
            architectures=architectures,
        )

        autopkgtest = workflow.children.get(
            task_name="autopkgtest", task_type=TaskTypes.WORKFLOW
        )

        collection_id = workflow.parent.internal_collection.id
        self.assertEqual(
            autopkgtest.task_data,
            {
                "architectures": ["amd64"],
                "backend": BackendType.AUTO,
                "binary_artifacts": [
                    f"{collection_id}@collections/name:build-amd64"
                ],
                "codename": "bookworm",
                "source_artifact": f"{source_artifact.id}@artifacts",
                "vendor": "debian",
            },
        )
        self.assertEqual(
            autopkgtest.workflow_data_json,
            {"display_name": "autopkgtest", "step": "autopkgtest"},
        )

        self.assertQuerySetEqual(
            autopkgtest.dependencies.all(),
            WorkRequest.objects.filter(
                task_name="sbuild", task_data={"host_architecture": "amd64"}
            ),
            ordered=False,
        )

        # AutopkgtestWorkflow.populate() was called and created its tasks
        self.assertTrue(autopkgtest.children.exists())

        lintian = workflow.children.get(
            task_name="lintian", task_type=TaskTypes.WORKFLOW
        )

        self.assertEqual(
            lintian.task_data,
            {
                "architectures": ["amd64"],
                "backend": BackendType.AUTO,
                "binary_artifacts": [
                    f"{collection_id}@collections/name:build-amd64"
                ],
                "codename": "bookworm",
                "fail_on_severity": "none",
                "source_artifact": f"{source_artifact.id}@artifacts",
                "vendor": "debian",
            },
        )
        self.assertEqual(
            lintian.workflow_data_json,
            {"display_name": "lintian", "step": "lintian"},
        )

        self.assertQuerySetEqual(
            lintian.dependencies.all(),
            WorkRequest.objects.filter(
                task_name="sbuild", task_data={"host_architecture": "amd64"}
            ),
            ordered=False,
        )
        # LintianWorkflow.populate() was called and created its tasks
        self.assertTrue(lintian.children.exists())

        piuparts = workflow.children.get(
            task_name="piuparts", task_type=TaskTypes.WORKFLOW
        )

        collection_id = workflow.parent.internal_collection.id

        self.assertEqual(
            piuparts.task_data,
            {
                "arch_all_host_architecture": "amd64",
                "architectures": ["amd64"],
                "backend": BackendType.AUTO,
                "binary_artifacts": [
                    f"{collection_id}@collections/name:build-amd64"
                ],
                "codename": "bookworm",
                "vendor": "debian",
            },
        )

        self.assertEqual(
            piuparts.workflow_data_json,
            {"display_name": "piuparts", "step": "piuparts"},
        )

        self.assertQuerySetEqual(
            piuparts.dependencies.all(),
            WorkRequest.objects.filter(
                task_name="sbuild", task_data={"host_architecture": "amd64"}
            ),
            ordered=False,
        )

        # PiupartsWorkflow.populate() was called and created its tasks
        self.assertTrue(piuparts.children.exists())

    @preserve_task_registry()
    def test_populate_disable_autopkgtest_lintian_piuparts(self):
        """Populate does not create autopkgtest, lintian nor piuparts."""
        source_artifact = self.playground.create_source_artifact(name="hello")
        binary_artifact = self.create_binary_packages(
            "hello", "hello", "1.0.0", "amd64"
        )
        workflow = self.orchestrate(
            extra_data={
                "source_artifact": f"{source_artifact.id}@artifacts",
                "binary_artifacts": [f"{binary_artifact.id}@artifacts"],
                "enable_autopkgtest": False,
                "enable_lintian": False,
                "enable_piuparts": False,
            },
        )

        self.assertFalse(
            workflow.children.filter(
                task_name="autopkgtest", task_type=TaskTypes.WORKFLOW
            ).exists()
        )
        self.assertFalse(
            workflow.children.filter(
                task_name="lintian", task_type=TaskTypes.WORKFLOW
            ).exists()
        )

        self.assertFalse(
            workflow.children.filter(
                task_name="piuparts", task_type=TaskTypes.WORKFLOW
            ).exists()
        )

    @preserve_task_registry()
    def test_populate_architectures_allowed(self):
        """Populate uses architectures and architectures_allowed."""
        source_artifact = self.playground.create_source_artifact(name="hello")
        binary_artifact_amd = self.create_binary_packages(
            "hello", "hello", "1.0.0", "amd64"
        )
        binary_artifact_i386 = self.create_binary_packages(
            "hello", "hello", "1.0.0", "i386"
        )
        binary_artifact_arm64 = self.create_binary_packages(
            "hello", "hello", "1.0.0", "arm64"
        )

        workflow = self.orchestrate(
            extra_data={
                "source_artifact": f"{source_artifact.id}@artifacts",
                "binary_artifacts": [
                    f"{binary_artifact_amd.id}@artifacts",
                    f"{binary_artifact_i386.id}@artifacts",
                    f"{binary_artifact_arm64.id}@artifacts",
                ],
                "architectures": ["amd64", "i386"],
                "architectures_allowlist": ["i386"],
            },
        )

        autopkgtest = workflow.children.get(
            task_name="autopkgtest", task_type=TaskTypes.WORKFLOW
        )

        self.assertEqual(
            autopkgtest.task_data,
            {
                "architectures": ["i386"],
                "backend": BackendType.AUTO,
                "binary_artifacts": [
                    f"{binary_artifact_i386.id}@artifacts",
                ],
                "codename": "bookworm",
                "source_artifact": f"{source_artifact.id}@artifacts",
                "vendor": "debian",
            },
        )

    @preserve_task_registry()
    def test_architectures_deny(self):
        """Populate uses architectures and architectures_denylist."""
        source_artifact = self.playground.create_source_artifact(name="hello")
        binary_artifact_amd = self.create_binary_packages(
            "hello", "hello", "1.0.0", "amd64"
        )
        binary_artifact_i386 = self.create_binary_packages(
            "hello", "hello", "1.0.0", "i386"
        )
        binary_artifact_arm64 = self.create_binary_packages(
            "hello", "hello", "1.0.0", "arm64"
        )

        workflow = self.orchestrate(
            extra_data={
                "source_artifact": f"{source_artifact.id}@artifacts",
                "binary_artifacts": [
                    f"{binary_artifact_amd.id}@artifacts",
                    f"{binary_artifact_i386.id}@artifacts",
                    f"{binary_artifact_arm64.id}@artifacts",
                ],
                "architectures": ["amd64", "i386"],
                "architectures_denylist": ["i386"],
            },
            architectures=["amd64", "i386"],
        )

        autopkgtest = workflow.children.get(
            task_name="autopkgtest", task_type=TaskTypes.WORKFLOW
        )

        self.assertEqual(
            autopkgtest.task_data,
            {
                "architectures": ["amd64"],
                "backend": BackendType.AUTO,
                "binary_artifacts": [
                    f"{binary_artifact_amd.id}@artifacts",
                ],
                "codename": "bookworm",
                "source_artifact": f"{source_artifact.id}@artifacts",
                "vendor": "debian",
            },
        )

    def test_get_label(self):
        """Test get_label()."""
        w = self.create_qa_workflow(extra_task_data={})
        self.assertEqual(w.get_label(), "run QA")
