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

"""Workflow to update metadata for a single suite."""

import datetime as dt
from functools import cached_property

from django.db import connection
from django.db.models import Q

from debusine.artifacts.models import ArtifactCategory, CollectionCategory
from debusine.client.models import LookupChildType
from debusine.db.models import Collection, WorkRequest
from debusine.server.collections.lookup import lookup_single
from debusine.server.tasks.models import GenerateSuiteIndexesData
from debusine.server.workflows.base import Workflow
from debusine.server.workflows.models import (
    UpdateSuiteData,
    WorkRequestWorkflowData,
)
from debusine.signing.models import SigningMode
from debusine.signing.tasks.models import SignRepositoryIndexData
from debusine.tasks import DefaultDynamicData
from debusine.tasks.models import (
    ActionUpdateCollectionWithArtifacts,
    BaseDynamicTaskData,
)
from debusine.tasks.server import TaskDatabaseInterface


class UpdateSuiteWorkflow(
    Workflow[UpdateSuiteData, BaseDynamicTaskData],
    DefaultDynamicData[UpdateSuiteData],
):
    """Update metadata for a single suite."""

    TASK_NAME = "update_suite"

    def _get_current_transaction_timestamp(self) -> dt.datetime:
        with connection.cursor() as cursor:
            cursor.execute("SELECT CURRENT_TIMESTAMP")
            [now] = cursor.fetchone()
        assert isinstance(now, dt.datetime)
        return now

    @cached_property
    def suite_collection(self) -> Collection:
        """The suite whose indexes should be generated."""
        suite = lookup_single(
            self.data.suite_collection,
            self.workspace,
            user=self.work_request.created_by,
            workflow_root=self.work_request.workflow_root,
            expect_type=LookupChildType.COLLECTION,
        ).collection
        self.ensure_collection_category(
            configuration_key="suite_collection",
            category=suite.category,
            expected=CollectionCategory.SUITE,
        )
        return suite

    def build_dynamic_data(
        self,
        task_database: TaskDatabaseInterface,  # noqa: U100
    ) -> BaseDynamicTaskData:
        """
        Compute dynamic data for this workflow.

        :subject: name of ``suite_collection``
        """
        return BaseDynamicTaskData(subject=self.suite_collection.name)

    def populate(self) -> None:
        """Create child work requests."""
        # TODO: It would be useful to be able to run this workflow for a
        # given timestamp in order to backfill history.
        now = self._get_current_transaction_timestamp()
        generate_task = self.work_request_ensure_child_server(
            task_name="generatesuiteindexes",
            task_data=GenerateSuiteIndexesData(
                suite_collection=self.data.suite_collection, generate_at=now
            ),
            workflow_data=WorkRequestWorkflowData(
                display_name=(
                    f"Generate indexes for {self.suite_collection.name}"
                ),
                step=f"generate-suite-indexes-{self.suite_collection.name}",
            ),
            task_data_filter=Q(
                task_data__suite_collection=self.data.suite_collection
            ),
        )
        generated_release_name = (
            f"generated-release-{self.suite_collection.name}"
        )
        self.provides_artifact(
            generate_task,
            ArtifactCategory.REPOSITORY_INDEX,
            generated_release_name,
            artifact_filters={"data__path": "Release"},
        )
        generated_release = (
            f"internal@collections/name:{generated_release_name}"
        )

        sign_tasks: dict[str, WorkRequest] = {}
        for mode, signed_name in (
            (SigningMode.DETACHED, "Release.gpg"),
            (SigningMode.CLEAR, "InRelease"),
        ):
            sign_tasks[signed_name] = self.work_request_ensure_child_signing(
                task_name="signrepositoryindex",
                task_data=SignRepositoryIndexData(
                    suite_collection=self.data.suite_collection,
                    unsigned=generated_release,
                    mode=mode,
                    signed_name=signed_name,
                ),
                workflow_data=WorkRequestWorkflowData(
                    display_name=(
                        f"Create signed {signed_name} for "
                        f"{self.suite_collection.name}"
                    ),
                    step=f"sign-{mode}-{self.suite_collection.name}",
                ),
            )
            self.requires_artifact(sign_tasks[signed_name], generated_release)
            sign_tasks[signed_name].add_event_reaction(
                "on_success",
                ActionUpdateCollectionWithArtifacts(
                    collection=self.data.suite_collection,
                    variables={"path": signed_name},
                    artifact_filters={
                        "category": ArtifactCategory.REPOSITORY_INDEX
                    },
                    created_at=now,
                ),
            )
        # Make sure InRelease is generated last, in order that the "current"
        # view of a suite can arrange to only show a snapshot once an
        # InRelease file exists for it.
        sign_tasks["InRelease"].add_dependency(sign_tasks["Release.gpg"])
