# 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 lintian workflow."""
from collections.abc import Sequence
from typing import Any

from debusine.artifacts.models import ArtifactCategory
from debusine.db.models import Artifact, WorkRequest, WorkflowTemplate
from debusine.server.workflows import LintianWorkflow, Workflow
from debusine.server.workflows.base import 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 LintianWorkflowTests(TestCase):
    """Unit tests for :py:class:`LintianWorkflow`."""

    def create_lintian_workflow(
        self,
        *,
        extra_task_data: dict[str, Any],
    ) -> LintianWorkflow:
        """Create a lintian workflow."""
        task_data = {
            "source_artifact": "20@artifacts",
            "binary_artifacts": [
                "internal@collections/name:build-amd64",
                "internal@collections/name:build-i386",
            ],
            "vendor": "debian",
            "codename": "bookworm",
        }
        task_data.update(extra_task_data)
        wr = self.playground.create_workflow(
            task_name="lintian", task_data=task_data
        )
        return LintianWorkflow(wr)

    def orchestrate(
        self, task_data: dict[str, Any], architectures: Sequence[str]
    ) -> WorkRequest:
        """Create and orchestrate a LintianWorkflow."""

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

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

                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},
                    )

                lintian = self.work_request.create_child(
                    task_type=TaskTypes.WORKFLOW,
                    task_name="lintian",
                    task_data=task_data,
                )
                LintianWorkflow(lintian).populate()

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

        root.mark_running()
        orchestrate_workflow(root)

        return root

    def create_binary_package(
        self, srcpkg_name: str, srcpkg_version: str, architecture: str
    ) -> Artifact:
        """Create a minimal `debian:binary-package` artifact."""
        artifact, _ = self.create_artifact(
            category=ArtifactCategory.BINARY_PACKAGE,
            data={
                "srcpkg_name": srcpkg_name,
                "srcpkg_version": srcpkg_version,
                "deb_fields": {"Architecture": architecture},
                "deb_control_files": [],
            },
        )
        return artifact

    @preserve_task_registry()
    def test_populate(self):
        """Test populate."""
        architectures = ("amd64", "i386", "all")

        source_artifact = self.playground.create_source_artifact(name="hello")

        self.create_binary_package("hello", "1.0.0", "arm64")

        root = self.orchestrate(
            task_data={
                "architectures": architectures,
                "source_artifact": f"{source_artifact.id}@artifacts",
                "binary_artifacts": [
                    f"internal@collections/name:build-{arch}"
                    for arch in architectures
                ],
                "vendor": "debian",
                "codename": "trixie",
                "backend": "schroot",
            },
            architectures=architectures,
        )

        workflow = WorkRequest.objects.get(
            task_type=TaskTypes.WORKFLOW,
            task_name="lintian",
            parent=root,
        )

        lintians = workflow.children.filter(task_name="lintian")

        # architecture "all" does not have a lintian job
        self.assertEqual(lintians.count(), len(architectures) - 1)

        collection_id = root.internal_collection.id

        for architecture in architectures:
            if architecture == "all":
                continue

            lintian = lintians.get(
                workflow_data_json__step=f"lintian-{architecture}"
            )

            self.assertEqual(
                lintian.task_data,
                {
                    "environment": "debian/match:codename=trixie",
                    "exclude_tags": [],
                    "fail_on_severity": "none",
                    "include_tags": [],
                    "input": {
                        "binary_artifacts": sorted(
                            [
                                f"{collection_id}@collections/name:build-{arch}"
                                for arch in (architecture, "all")
                            ]
                        ),
                        "source_artifact": f"{source_artifact.id}@artifacts",
                    },
                    "backend": BackendType.SCHROOT,
                    "output": {},
                    "target_distribution": "debian:trixie",
                },
            )

            self.assertEqual(
                lintian.workflow_data_json,
                {
                    "display_name": f"Lintian for {architecture}",
                    "step": f"lintian-{architecture}",
                },
            )

            self.assertQuerySetEqual(
                lintian.dependencies.all(),
                WorkRequest.objects.filter(
                    task_name="sbuild",
                    task_data__host_architecture__in=(architecture, "all"),
                ),
                ordered=False,
            )

    @preserve_task_registry()
    def test_populate_without_architectures(self):
        """Test populate."""
        source_artifact = self.playground.create_source_artifact(name="hello")

        binary_artifact = self.create_binary_package("hello", "1.0.0", "all")

        root = self.orchestrate(
            task_data={
                "source_artifact": f"{source_artifact.id}@artifacts",
                "binary_artifacts": [f"{binary_artifact.id}@artifacts"],
                "vendor": "debian",
                "codename": "trixie",
            },
            architectures=("all",),
        )

        workflow = WorkRequest.objects.get(
            task_type=TaskTypes.WORKFLOW,
            task_name="lintian",
            parent=root,
        )

        lintian = workflow.children.get(task_name="lintian")

        self.assertEqual(
            lintian.task_data,
            {
                "environment": "debian/match:codename=trixie",
                "exclude_tags": [],
                "fail_on_severity": "none",
                "include_tags": [],
                "input": {
                    "binary_artifacts": [f"{binary_artifact.id}@artifacts"],
                    "source_artifact": f"{source_artifact.id}@artifacts",
                },
                "backend": BackendType.UNSHARE,
                "output": {},
                "target_distribution": "debian:trixie",
            },
        )

    @preserve_task_registry()
    def test_orchestrate_idempotent(self):
        """Calling orchestrate twice does not create new work requests."""
        source = self.playground.create_source_artifact(name="hello")
        binary = self.create_binary_package("hello", "1.0.0", "all")

        wr = WorkRequest.objects.create_workflow(
            template=WorkflowTemplate(
                name="lintian",
                workspace=self.playground.get_default_workspace(),
                task_name="lintian",
            ),
            data={
                "source_artifact": f"{source.id}@artifacts",
                "binary_artifacts": [f"{binary.id}@artifacts"],
                "vendor": "debian",
                "codename": "trixie",
            },
            created_by=self.playground.get_default_user(),
        )

        LintianWorkflow(wr).populate()

        children = set(wr.children.all())

        LintianWorkflow(wr).populate()

        self.assertQuerySetEqual(wr.children.all(), children, ordered=False)

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