# 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 Autopkgtest work request plugin."""


from django.template.loader import get_template
from django.test import TestCase
from django.urls import reverse

from debusine.artifacts import AutopkgtestArtifact
from debusine.artifacts.models import ArtifactCategory, CollectionCategory
from debusine.db.models import Artifact, WorkRequest
from debusine.tasks.models import (
    AutopkgtestDynamicData,
    LookupMultiple,
    LookupSingle,
)
from debusine.test import TestHelpersMixin
from debusine.web.views.autopkgtest import AutopkgtestView


class AutopkgtestViewTests(TestHelpersMixin, TestCase):
    """Tests for AutopkgtestView."""

    def create_work_request_for_artifacts(
        self,
        environment_artifact: Artifact,
        source_artifact: Artifact,
        binary_artifacts: list[Artifact],
        context_artifacts: list[Artifact] | None = None,
    ) -> WorkRequest:
        """Create an autopkgtest work request for some artifacts."""
        environment_lookup: int | str
        if "vendor" in environment_artifact.data:
            environments_collection = self.create_collection(
                environment_artifact.data["vendor"],
                CollectionCategory.ENVIRONMENTS,
            )
            environments_collection.manager.add_artifact(
                environment_artifact,
                user=self.get_test_user(),
                variables={"backend": "unshare"},
            )
            environment_lookup = "{vendor}/match:codename={codename}".format(
                **environment_artifact.data
            )
        else:
            environment_lookup = environment_artifact.id
        task_data_input = {
            "source_artifact": source_artifact.id,
            "binary_artifacts": [
                binary_artifact.id for binary_artifact in binary_artifacts
            ],
        }
        if context_artifacts:
            task_data_input["context_artifacts"] = [
                context_artifact.id for context_artifact in context_artifacts
            ]

        return self.create_work_request(
            result=WorkRequest.Results.SUCCESS,
            task_data={
                "host_architecture": "amd64",
                "environment": environment_lookup,
                "input": task_data_input,
            },
            task_name="autopkgtest",
        )

    def create_autopkgtest_artifact(
        self, work_request: WorkRequest
    ) -> Artifact:
        """Create an AutopkgtestArtifact for a work request."""
        artifact, _ = self.create_artifact(
            paths=["log", "summary"],
            work_request=work_request,
            create_files=True,
        )
        artifact.category = AutopkgtestArtifact._category
        artifact.data = {
            "results": {
                "command1": {"status": "PASS"},
                "command2": {"status": "FAIL", "details": "partial"},
            }
        }
        artifact.save()
        return artifact

    def test_get_context_data_simple(self) -> None:
        """get_context_data returns the expected data for a simple case."""
        environment_artifact, _ = self.create_artifact(
            paths=["system.tar.xz"],
            category=ArtifactCategory.SYSTEM_TARBALL,
            data={
                "vendor": "debian",
                "codename": "bookworm",
                "architecture": "amd64",
                "variant": "",
            },
            create_files=True,
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category=ArtifactCategory.SOURCE_PACKAGE,
            data={"name": "hello", "version": "1.0", "type": "dpkg"},
            create_files=True,
        )
        binary_artifact_1, _ = self.create_artifact(
            paths=["hello-1.deb"], create_files=True
        )
        binary_artifact_2, _ = self.create_artifact(
            paths=["hello-2.deb"], create_files=True
        )
        work_request: WorkRequest = self.create_work_request_for_artifacts(
            environment_artifact,
            source_artifact,
            [binary_artifact_1, binary_artifact_2],
        )
        work_request.dynamic_task_data = AutopkgtestDynamicData(
            environment_id=environment_artifact.id,
            input_source_artifact_id=source_artifact.id,
            input_binary_artifacts_ids=[
                binary_artifact_1.id,
                binary_artifact_2.id,
            ],
        ).dict()
        work_request.save()
        result_artifact = self.create_autopkgtest_artifact(work_request)
        autopkgtest_view = AutopkgtestView()

        context_data = autopkgtest_view.get_context_data(work_request)

        self.assertEqual(
            context_data,
            {
                "request_data": {
                    "backend": "auto",
                    "binary_artifacts": {
                        "lookup": [binary_artifact_1.id, binary_artifact_2.id],
                        "artifacts": [
                            {
                                "files": ["hello-1.deb"],
                                "id": binary_artifact_1.id,
                            },
                            {
                                "files": ["hello-2.deb"],
                                "id": binary_artifact_2.id,
                            },
                        ],
                    },
                    "codename": "bookworm",
                    "context_artifacts": {"lookup": [], "artifacts": []},
                    "debug_level": 0,
                    "exclude_tests": [],
                    "extra_apt_sources": [],
                    "extra_environment": {},
                    "fail_on_scenarios": ["failed"],
                    "host_architecture": "amd64",
                    "include_tests": [],
                    "needs_internet": "run",
                    "source_artifact": {
                        "files": ["hello.dsc"],
                        "id": source_artifact.id,
                        "lookup": source_artifact.id,
                    },
                    "source_name": "hello",
                    "source_version": "1.0",
                    "task_description": "hello_1.0_amd64 in debian:bookworm",
                    "timeout": None,
                    "use_packages_from_base_repository": False,
                    "vendor": "debian",
                },
                "result": work_request.result,
                "result_artifact": result_artifact,
                "result_data": [
                    ("command1", "PASS", None),
                    ("command2", "FAIL", "partial"),
                ],
            },
        )

    def test_get_context_data_with_context_artifacts(self) -> None:
        """Any context artifacts show up in the context data."""
        environment_artifact, _ = self.create_artifact(
            paths=["system.tar.xz"],
            category=ArtifactCategory.SYSTEM_TARBALL,
            data={
                "vendor": "debian",
                "codename": "bookworm",
                "architecture": "amd64",
                "variant": "",
            },
            create_files=True,
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category=ArtifactCategory.SOURCE_PACKAGE,
            data={"name": "hello", "version": "1.0", "type": "dpkg"},
            create_files=True,
        )
        context_artifact_1, _ = self.create_artifact(
            paths=["hello-rdep-1.deb"], create_files=True
        )
        context_artifact_2, _ = self.create_artifact(
            paths=["hello-rdep-2.deb"], create_files=True
        )
        work_request: WorkRequest = self.create_work_request_for_artifacts(
            environment_artifact,
            source_artifact,
            [],
            context_artifacts=[context_artifact_1, context_artifact_2],
        )
        work_request.dynamic_task_data = AutopkgtestDynamicData(
            environment_id=environment_artifact.id,
            input_source_artifact_id=source_artifact.id,
            input_binary_artifacts_ids=[],
            input_context_artifacts_ids=[
                context_artifact_1.id,
                context_artifact_2.id,
            ],
        ).dict()
        work_request.save()
        autopkgtest_view = AutopkgtestView()

        context_data = autopkgtest_view.get_context_data(work_request)

        self.assertEqual(
            context_data["request_data"]["context_artifacts"],
            {
                "lookup": [context_artifact_1.id, context_artifact_2.id],
                "artifacts": [
                    {
                        "files": ["hello-rdep-1.deb"],
                        "id": context_artifact_1.id,
                    },
                    {
                        "files": ["hello-rdep-2.deb"],
                        "id": context_artifact_2.id,
                    },
                ],
            },
        )

    def test_context_data_no_artifacts(self) -> None:
        """If there are no output artifacts, context data omits that info."""
        environment_artifact, _ = self.create_artifact(
            paths=["system.tar.xz"],
            category=ArtifactCategory.SYSTEM_TARBALL,
            data={
                "vendor": "debian",
                "codename": "bookworm",
                "architecture": "amd64",
                "variant": "",
            },
            create_files=True,
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category=ArtifactCategory.SOURCE_PACKAGE,
            data={"name": "hello", "version": "1.0", "type": "dpkg"},
            create_files=True,
        )
        work_request: WorkRequest = self.create_work_request_for_artifacts(
            environment_artifact, source_artifact, []
        )
        autopkgtest_view = AutopkgtestView()

        context_data = autopkgtest_view.get_context_data(work_request)
        self.assertCountEqual(context_data.keys(), {"request_data", "result"})

    def test_context_data_no_dynamic_task_data(self) -> None:
        """If there is no dynamic data, context data omits artifact info."""
        environment_artifact, _ = self.create_artifact(
            category=ArtifactCategory.SYSTEM_TARBALL
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category=ArtifactCategory.SOURCE_PACKAGE,
            data={"name": "hello", "version": "1.0", "type": "dpkg"},
            create_files=True,
        )
        work_request: WorkRequest = self.create_work_request_for_artifacts(
            environment_artifact, source_artifact, []
        )
        autopkgtest_view = AutopkgtestView()

        context_data = autopkgtest_view.get_context_data(work_request)
        self.assertIsNone(context_data["request_data"]["codename"])
        self.assertIsNone(context_data["request_data"]["vendor"])
        self.assertEqual(
            context_data["request_data"]["task_description"],
            "UNKNOWN_UNKNOWN_amd64",
        )
        self.assertEqual(
            context_data["request_data"]["source_artifact"],
            {"lookup": source_artifact.id, "id": None, "files": []},
        )

    def test_context_data_missing_environment_artifact(self) -> None:
        """If the environment artifact is missing, context data omits it."""
        environment_artifact, _ = self.create_artifact(
            category=ArtifactCategory.SYSTEM_TARBALL
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category=ArtifactCategory.SOURCE_PACKAGE,
            data={"name": "hello", "version": "1.0", "type": "dpkg"},
            create_files=True,
        )
        work_request: WorkRequest = self.create_work_request_for_artifacts(
            environment_artifact, source_artifact, []
        )
        work_request.dynamic_task_data = AutopkgtestDynamicData(
            environment_id=environment_artifact.id,
            input_source_artifact_id=source_artifact.id,
            input_binary_artifacts_ids=[],
        ).dict()
        work_request.save()
        environment_artifact.delete()
        autopkgtest_view = AutopkgtestView()

        context_data = autopkgtest_view.get_context_data(work_request)
        self.assertIsNone(context_data["request_data"]["codename"])
        self.assertIsNone(context_data["request_data"]["vendor"])
        self.assertEqual(
            context_data["request_data"]["task_description"], "hello_1.0_amd64"
        )

    def test_context_data_environment_artifact_wrong_category(self) -> None:
        """We cope with the environment artifact having an odd category."""
        environment_artifact, _ = self.create_artifact(
            paths=["system.tar.xz"],
            category=ArtifactCategory.TEST,
            create_files=True,
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category=ArtifactCategory.SOURCE_PACKAGE,
            data={"name": "hello", "version": "1.0", "type": "dpkg"},
            create_files=True,
        )
        work_request: WorkRequest = self.create_work_request_for_artifacts(
            environment_artifact, source_artifact, []
        )
        work_request.dynamic_task_data = AutopkgtestDynamicData(
            environment_id=environment_artifact.id,
            input_source_artifact_id=source_artifact.id,
            input_binary_artifacts_ids=[],
        ).dict()
        work_request.save()
        autopkgtest_view = AutopkgtestView()

        context_data = autopkgtest_view.get_context_data(work_request)
        self.assertIsNone(context_data["request_data"]["codename"])
        self.assertIsNone(context_data["request_data"]["vendor"])
        self.assertEqual(
            context_data["request_data"]["task_description"], "hello_1.0_amd64"
        )

    def test_context_data_missing_source_artifact(self) -> None:
        """If the source artifact is missing, context data omits its files."""
        environment_artifact, _ = self.create_artifact(
            category=ArtifactCategory.SYSTEM_TARBALL
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category=ArtifactCategory.SOURCE_PACKAGE,
            data={"name": "hello", "version": "1.0", "type": "dpkg"},
            create_files=True,
        )
        work_request: WorkRequest = self.create_work_request_for_artifacts(
            environment_artifact, source_artifact, []
        )
        work_request.dynamic_task_data = AutopkgtestDynamicData(
            environment_id=environment_artifact.id,
            input_source_artifact_id=source_artifact.id,
            input_binary_artifacts_ids=[],
        ).dict()
        work_request.save()
        source_artifact_id = source_artifact.id
        source_artifact.files.clear()
        source_artifact.delete()
        autopkgtest_view = AutopkgtestView()

        context_data = autopkgtest_view.get_context_data(work_request)
        self.assertEqual(
            context_data["request_data"]["source_artifact"],
            {
                "lookup": source_artifact_id,
                "id": source_artifact_id,
                "files": [],
            },
        )

    def test_context_data_source_artifact_wrong_category(self) -> None:
        """We cope with the source artifact having an odd category."""
        environment_artifact, _ = self.create_artifact(
            paths=["system.tar.xz"],
            category=ArtifactCategory.SYSTEM_TARBALL,
            data={
                "vendor": "debian",
                "codename": "bookworm",
                "architecture": "amd64",
                "variant": "",
            },
            create_files=True,
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category=ArtifactCategory.TEST,
            create_files=True,
        )
        work_request: WorkRequest = self.create_work_request_for_artifacts(
            environment_artifact, source_artifact, []
        )
        work_request.dynamic_task_data = AutopkgtestDynamicData(
            environment_id=environment_artifact.id,
            input_source_artifact_id=source_artifact.id,
            input_binary_artifacts_ids=[],
        ).dict()
        work_request.save()
        autopkgtest_view = AutopkgtestView()

        context_data = autopkgtest_view.get_context_data(work_request)
        self.assertIsNone(context_data["request_data"]["source_name"])
        self.assertIsNone(context_data["request_data"]["source_version"])
        self.assertEqual(
            context_data["request_data"]["task_description"],
            "UNKNOWN_UNKNOWN_amd64 in debian:bookworm",
        )

    def test_context_data_missing_binary_artifact(self) -> None:
        """If a binary artifact is missing, context data omits its files."""
        environment_artifact, _ = self.create_artifact(
            category=ArtifactCategory.SYSTEM_TARBALL
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category=ArtifactCategory.SOURCE_PACKAGE,
            data={"name": "hello", "version": "1.0", "type": "dpkg"},
            create_files=True,
        )
        binary_artifact_1, _ = self.create_artifact(
            paths=["hello-1.deb"], create_files=True
        )
        binary_artifact_2, _ = self.create_artifact(
            paths=["hello-2.deb"], create_files=True
        )
        work_request: WorkRequest = self.create_work_request_for_artifacts(
            environment_artifact, source_artifact, []
        )
        work_request.dynamic_task_data = AutopkgtestDynamicData(
            environment_id=environment_artifact.id,
            input_source_artifact_id=source_artifact.id,
            input_binary_artifacts_ids=[
                binary_artifact_1.id,
                binary_artifact_2.id,
            ],
        ).dict()
        work_request.save()
        binary_artifact_1_id = binary_artifact_1.id
        binary_artifact_1.files.clear()
        binary_artifact_1.delete()
        autopkgtest_view = AutopkgtestView()

        context_data = autopkgtest_view.get_context_data(work_request)
        self.assertEqual(
            context_data["request_data"]["binary_artifacts"],
            {
                "lookup": work_request.task_data["input"]["binary_artifacts"],
                "artifacts": [
                    {"id": binary_artifact_1_id, "files": []},
                    {"id": binary_artifact_2.id, "files": ["hello-2.deb"]},
                ],
            },
        )

    def assert_contains_artifact_information(
        self,
        response,
        description: str,
        artifact_lookup: LookupSingle,
        artifact_id: int,
    ):
        """Assert contains information of an artifact: link and files."""
        files = (
            Artifact.objects.get(id=artifact_id)
            .fileinartifact_set.order_by("path")
            .values_list("path", flat=True)
        )
        artifact_path = reverse(
            "artifacts:detail", kwargs={"artifact_id": artifact_id}
        )
        artifact_link = f'<a href="{artifact_path}">#{artifact_id}</a>'
        files_li = "".join([f"<li>{file}</li>" for file in files])
        self.assertContains(
            response,
            f"<li>{description} ({artifact_lookup}: {artifact_link})"
            f"<ul>{files_li}</ul>"
            f"</li>",
            html=True,
        )

    def assert_contains_artifacts_information(
        self,
        response,
        description: str,
        artifact_lookup: LookupMultiple,
        artifact_ids: list[int],
    ):
        """Assert contains information of a set of artifacts."""
        expected_response = (
            f"<li>{description} ({artifact_lookup.export()})<ul>"
        )
        for artifact_id in artifact_ids:
            files = (
                Artifact.objects.get(id=artifact_id)
                .fileinartifact_set.order_by("path")
                .values_list("path", flat=True)
            )
            artifact_path = reverse(
                "artifacts:detail", kwargs={"artifact_id": artifact_id}
            )
            artifact_link = f'<a href="{artifact_path}">#{artifact_id}</a>'
            files_li = "".join([f"<li>{file}</li>" for file in files])
            expected_response += f"<li>{artifact_link}<ul>{files_li}</ul></li>"
        expected_response += "</ul></li>"
        self.assertContains(response, expected_response, html=True)

    def test_template_output(self) -> None:
        """Generic output of the template."""
        environment_artifact, _ = self.create_artifact(
            paths=["system.tar.xz"],
            category=ArtifactCategory.SYSTEM_TARBALL,
            data={
                "vendor": "debian",
                "codename": "bookworm",
                "architecture": "amd64",
                "variant": "",
            },
            create_files=True,
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category=ArtifactCategory.SOURCE_PACKAGE,
            data={"name": "hello", "version": "1.0", "type": "dpkg"},
            create_files=True,
        )
        binary_artifact_1, _ = self.create_artifact(
            paths=["hello-1.deb"], create_files=True
        )
        binary_artifact_2, _ = self.create_artifact(
            paths=["hello-2.deb"], create_files=True
        )
        work_request: WorkRequest = self.create_work_request_for_artifacts(
            environment_artifact,
            source_artifact,
            [binary_artifact_1, binary_artifact_2],
        )
        work_request.dynamic_task_data = AutopkgtestDynamicData(
            environment_id=environment_artifact.id,
            input_source_artifact_id=source_artifact.id,
            input_binary_artifacts_ids=[
                binary_artifact_1.id,
                binary_artifact_2.id,
            ],
        ).dict()
        work_request.save()
        self.create_autopkgtest_artifact(work_request)

        response = self.client.get(
            reverse("work_requests:detail", kwargs={"pk": work_request.id})
        )

        self.assertContains(
            response,
            "<title>Debusine - Autopkgtest run for hello_1.0_amd64 in "
            "debian:bookworm</title>",
            html=True,
        )
        self.assertContains(
            response,
            "<h1>Autopkgtest run for hello_1.0_amd64 in debian:bookworm</h1>",
            html=True,
        )
        work_request_generic_path = (
            reverse("work_requests:detail", kwargs={"pk": work_request.id})
            + "?view=generic"
        )
        self.assertContains(
            response,
            f'<a href="{work_request_generic_path}">Generic view</a>',
            html=True,
        )
        result_output = get_template("web/_work_request-result.html").render(
            {"result": work_request.result}
        )
        self.assertContains(response, f"Result: {result_output}", html=True)

        self.assert_contains_artifact_information(
            response, "Source artifact", source_artifact.id, source_artifact.id
        )

        self.assert_contains_artifacts_information(
            response,
            "Binary artifacts",
            LookupMultiple.parse_obj(
                [binary_artifact_1.id, binary_artifact_2.id]
            ),
            [binary_artifact_1.id, binary_artifact_2.id],
        )

    def test_get_fail_on_scenarios(self):
        """_get_fail_on_scenarios processes the task's fail_on correctly."""
        sub_tests = [
            ({"failed_test": True}, ["failed"]),
            (
                {
                    "failed_test": False,
                    "flaky_test": True,
                    "skipped_test": False,
                },
                ["flaky"],
            ),
            (
                {
                    "failed_test": True,
                    "flaky_test": True,
                    "skipped_test": True,
                },
                ["failed", "flaky", "skipped"],
            ),
        ]

        for fail_on, expected_scenarios in sub_tests:
            with self.subTest(fail_on=fail_on):
                self.assertEqual(
                    AutopkgtestView._get_fail_on_scenarios(fail_on),
                    expected_scenarios,
                )
