# 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 typing import Optional

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

from debusine.artifacts import AutopkgtestArtifact
from debusine.db.models import Artifact, WorkRequest
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: Optional[list[Artifact]] = None,
    ) -> WorkRequest:
        """Create an autopkgtest work request for some artifacts."""
        task_data_input = {
            "source_artifact_id": source_artifact.id,
            "binary_artifacts_ids": [
                binary_artifact.id for binary_artifact in binary_artifacts
            ],
        }
        if context_artifacts:
            task_data_input["context_artifacts_ids"] = [
                context_artifact.id for context_artifact in context_artifacts
            ]

        return self.create_work_request(
            result=WorkRequest.Results.SUCCESS,
            task_data={
                "host_architecture": "amd64",
                "environment_id": environment_artifact.id,
                "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):
        """get_context_data returns the expected data for a simple case."""
        environment_artifact, _ = self.create_artifact(
            paths=["system.tar.xz"],
            category="debian:system-tarball",
            data={"vendor": "debian", "codename": "bookworm"},
            create_files=True,
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category="debian: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],
        )
        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": {
                    "host_architecture": "amd64",
                    "backend": "auto",
                    "binary_artifacts": [
                        {"files": ["hello-1.deb"], "id": binary_artifact_1.id},
                        {"files": ["hello-2.deb"], "id": binary_artifact_2.id},
                    ],
                    "codename": "bookworm",
                    "context_artifacts": [],
                    "debug_level": None,
                    "environment": None,
                    "exclude_tests": None,
                    "extra_apt_sources": None,
                    "fail_on_scenarios": [],
                    "include_tests": None,
                    "needs_internet": None,
                    "source_artifact": {
                        "files": ["hello.dsc"],
                        "id": 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": None,
                    "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):
        """Any context artifacts show up in the context data."""
        environment_artifact, _ = self.create_artifact(
            paths=["system.tar.xz"],
            category="debian:system-tarball",
            data={"vendor": "debian", "codename": "bookworm"},
            create_files=True,
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category="debian: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],
        )
        autopkgtest_view = AutopkgtestView()

        context_data = autopkgtest_view.get_context_data(work_request)

        self.assertEqual(
            context_data["request_data"]["context_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):
        """If there are no output artifacts, context data omits that info."""
        environment_artifact, _ = self.create_artifact(
            paths=["system.tar.xz"],
            category="debian:system-tarball",
            data={"vendor": "debian", "codename": "bookworm"},
            create_files=True,
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category="debian: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_missing_environment_artifact(self):
        """If the environment artifact is missing, context data omits it."""
        environment_artifact, _ = self.create_artifact(
            category="debian:system-tarball"
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category="debian: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, []
        )
        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):
        """We cope with the environment artifact having an odd category."""
        environment_artifact, _ = self.create_artifact(
            paths=["system.tar.xz"],
            category="unusual",
            create_files=True,
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category="debian: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"], "hello_1.0_amd64"
        )

    def test_context_data_source_artifact_wrong_category(self):
        """We cope with the source artifact having an odd category."""
        environment_artifact, _ = self.create_artifact(
            paths=["system.tar.xz"],
            category="debian:system-tarball",
            data={"vendor": "debian", "codename": "bookworm"},
            create_files=True,
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category="unusual",
            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"]["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 assert_contains_artifact_information(
        self, response, description: str, 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_link})"
            f"<ul>{files_li}</ul>"
            f"</li>",
            html=True,
        )

    def test_template_output(self):
        """Generic output of the template."""
        environment_artifact, _ = self.create_artifact(
            paths=["system.tar.xz"],
            category="debian:system-tarball",
            data={"vendor": "debian", "codename": "bookworm"},
            create_files=True,
        )
        source_artifact, _ = self.create_artifact(
            paths=["hello.dsc"],
            category="debian: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],
        )
        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
        )

        for binary_artifact in (binary_artifact_1, binary_artifact_2):
            self.assert_contains_artifact_information(
                response, "Binary artifact", binary_artifact.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,
                )
