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

"""Unit tests for the ``update_suite`` workflow."""

from typing import ClassVar, override

import pydantic_core
from django.db import connection

from debusine.artifacts.models import CollectionCategory, TaskTypes
from debusine.db.models import Collection, CollectionItem, WorkRequest
from debusine.server.workflows import UpdateSuiteWorkflow
from debusine.server.workflows.base import orchestrate_workflow
from debusine.server.workflows.models import UpdateSuiteData
from debusine.server.workflows.tests.helpers import WorkflowTestCase
from debusine.signing.models import SigningMode
from debusine.tasks.models import BaseDynamicTaskData


class UpdateSuiteWorkflowTests(WorkflowTestCase[UpdateSuiteWorkflow]):
    """Unit tests for :py:class:`UpdateSuiteWorkflow`."""

    SAMPLE_TASK_DATA = {"suite_collection": f"sid@{CollectionCategory.SUITE}"}

    sid: ClassVar[Collection]

    @override
    @classmethod
    def setUpTestData(cls) -> None:
        super().setUpTestData()
        cls.sid = cls.playground.create_collection(
            "sid", CollectionCategory.SUITE
        )

    def create_release_item(
        self, suite: Collection, work_request: WorkRequest
    ) -> CollectionItem:
        """Create a minimal debian:repository-index item in the suite."""
        return CollectionItem.objects.create_from_artifact(
            self.playground.create_repository_index(
                "Release", work_request=work_request
            ),
            parent_collection=suite,
            name="index:Release",
            data={"path": "Release"},
            created_by_user=self.playground.get_default_user(),
        )

    def test_compute_scheduler_tags(self) -> None:
        self.assertComputesSchedulerTags()

    def test_compute_dynamic_data(self) -> None:
        self.assertEqual(
            self.compute_dynamic_data(self.task),
            BaseDynamicTaskData(subject="sid"),
        )

    def test_populate(self) -> None:
        suite = self.sid
        workflow = self.playground.create_workflow(
            task_name="update_suite",
            task_data=UpdateSuiteData(
                suite_collection=f"sid@{CollectionCategory.SUITE}"
            ),
            mark_running=True,
        )

        self.assertTrue(orchestrate_workflow(workflow))

        with connection.cursor() as cursor:
            cursor.execute("SELECT CURRENT_TIMESTAMP")
            [now] = cursor.fetchone()
        self.assertQuerySetEqual(
            workflow.children.values_list(
                "task_type",
                "task_name",
                "task_data",
                "workflow_data_json",
                "status",
            ),
            [
                (
                    TaskTypes.SERVER,
                    "generatesuiteindexes",
                    {
                        "suite_collection": f"sid@{CollectionCategory.SUITE}",
                        "generate_at": pydantic_core.to_jsonable_python(now),
                    },
                    {
                        "display_name": "Generate indexes for sid",
                        "step": "generate-suite-indexes-sid",
                    },
                    WorkRequest.Statuses.PENDING,
                ),
                (
                    TaskTypes.SIGNING,
                    "signrepositoryindex",
                    {
                        "suite_collection": f"sid@{CollectionCategory.SUITE}",
                        "unsigned": (
                            "internal@collections/name:generated-release-sid"
                        ),
                        "mode": SigningMode.DETACHED,
                        "signed_name": "Release.gpg",
                    },
                    {
                        "display_name": "Create signed Release.gpg for sid",
                        "step": "sign-detached-sid",
                    },
                    WorkRequest.Statuses.BLOCKED,
                ),
                (
                    TaskTypes.SIGNING,
                    "signrepositoryindex",
                    {
                        "suite_collection": f"sid@{CollectionCategory.SUITE}",
                        "unsigned": (
                            "internal@collections/name:generated-release-sid"
                        ),
                        "mode": SigningMode.CLEAR,
                        "signed_name": "InRelease",
                    },
                    {
                        "display_name": "Create signed InRelease for sid",
                        "step": "sign-clear-sid",
                    },
                    WorkRequest.Statuses.BLOCKED,
                ),
            ],
        )
        self.assertQuerySetEqual(
            workflow.children.values_list(
                "workflow_data_json__step",
                "dependencies__workflow_data_json__step",
            ),
            [
                ("generate-suite-indexes-sid", None),
                ("sign-detached-sid", "generate-suite-indexes-sid"),
                ("sign-clear-sid", "generate-suite-indexes-sid"),
                ("sign-clear-sid", "sign-detached-sid"),
            ],
            ordered=False,
        )

        # Completing each step allows the next to proceed.
        generate_suite_indexes = workflow.children.get(
            workflow_data_json__step="generate-suite-indexes-sid"
        )
        release_item = self.create_release_item(suite, generate_suite_indexes)

        # Resolve the promise in the internal collection
        # FIXME: is there a better way to do this in tests?
        assert workflow.internal_collection is not None
        assert release_item.artifact is not None
        workflow.internal_collection.manager.add_artifact(
            release_item.artifact,
            user=release_item.created_by_user,
            workflow=workflow,
            name="generated-release-sid",
            variables=release_item.data,
            replace=True,
        )

        self.assertQuerySetEqual(
            suite.child_items.values_list("artifact", "data__path"),
            [(release_item.artifact_id, "Release")],
            ordered=False,
        )
        self.playground.advance_work_request(
            generate_suite_indexes, result=WorkRequest.Results.SUCCESS
        )

        sign_detached = workflow.children.get(
            workflow_data_json__step="sign-detached-sid"
        )
        self.assertEqual(sign_detached.status, WorkRequest.Statuses.PENDING)
        sign_clear = workflow.children.get(
            workflow_data_json__step="sign-clear-sid"
        )
        self.assertEqual(sign_clear.status, WorkRequest.Statuses.BLOCKED)

        release_gpg = self.playground.create_repository_index(
            "Release.gpg", work_request=sign_detached
        )
        self.playground.advance_work_request(
            sign_detached, result=WorkRequest.Results.SUCCESS
        )

        self.assertQuerySetEqual(
            suite.child_items.values_list("artifact", "data__path"),
            [
                (release_item.artifact_id, "Release"),
                (release_gpg.id, "Release.gpg"),
            ],
            ordered=False,
        )
        sign_clear.refresh_from_db()
        self.assertEqual(sign_clear.status, WorkRequest.Statuses.PENDING)

        inrelease = self.playground.create_repository_index(
            "InRelease", work_request=sign_clear
        )
        self.playground.advance_work_request(
            sign_clear, result=WorkRequest.Results.SUCCESS
        )

        self.assertQuerySetEqual(
            suite.child_items.values_list("artifact", "data__path"),
            [
                (release_item.artifact_id, "Release"),
                (release_gpg.id, "Release.gpg"),
                (inrelease.id, "InRelease"),
            ],
            ordered=False,
        )
