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

"""Manager for debusine:workflow-internal collections."""

from typing import Any

from django.db import IntegrityError
from django.utils import timezone

from debusine.artifacts.models import BareDataCategory, CollectionCategory
from debusine.db.models import Artifact, CollectionItem, User
from debusine.server.collections.base import (
    CollectionManagerInterface,
    ItemAdditionError,
)


class WorkflowInternalManager(CollectionManagerInterface):
    """
    Manage collection of category debusine:workflow-internal.

    This collection stores runtime data of a workflow.  Bare items can be
    used to store arbitrary JSON data, while artifact items can help to
    share artifacts between all the tasks (and help retain them for
    long-running workflows).
    """

    COLLECTION_CATEGORY = CollectionCategory.WORKFLOW_INTERNAL
    VALID_BARE_DATA_CATEGORIES = None
    VALID_ARTIFACT_CATEGORIES = None

    def do_add_bare_data(
        self,
        category: BareDataCategory,
        *,
        user: User,
        data: dict[str, Any] | None = None,  # noqa: U100
        name: str | None = None,
        replace: bool = False,
    ) -> CollectionItem:
        """Add bare data into the managed collection."""
        if name is None:
            raise ItemAdditionError(
                f"Adding to {CollectionCategory.WORKFLOW_INTERNAL} requires "
                f"an item name"
            )

        if replace:
            self.remove_bare_data(name, user=user)

        try:
            return CollectionItem.objects.create_from_bare_data(
                category,
                parent_collection=self.collection,
                created_by_user=user,
                name=name,
                data=data or {},
            )
        except IntegrityError as exc:
            raise ItemAdditionError(str(exc))

    def do_remove_bare_data(
        self, name: str, *, user: User | None = None
    ) -> None:
        """Remove a bare data item from the collection."""
        CollectionItem.active_objects.filter(
            name=name, parent_collection=self.collection
        ).update(removed_by_user=user, removed_at=timezone.now())

    def do_add_artifact(
        self,
        artifact: Artifact,
        *,
        user: User,
        variables: dict[str, Any] | None = None,  # noqa: U100
        name: str | None = None,
        replace: bool = False,
    ) -> CollectionItem:
        """Add the artifact into the managed collection."""
        if name is None:
            raise ItemAdditionError(
                f"Adding to {CollectionCategory.WORKFLOW_INTERNAL} requires "
                f"an item name"
            )

        if replace:
            try:
                old_artifact = (
                    CollectionItem.active_objects.filter(
                        parent_collection=self.collection,
                        child_type=CollectionItem.Types.ARTIFACT,
                        name=name,
                    )
                    .latest("created_at")
                    .artifact
                )
            except CollectionItem.DoesNotExist:
                pass
            else:
                assert old_artifact is not None
                self.remove_artifact(old_artifact, user=user)

        try:
            return CollectionItem.objects.create_from_artifact(
                artifact,
                parent_collection=self.collection,
                created_by_user=user,
                name=name,
                data={},
            )
        except IntegrityError as exc:
            raise ItemAdditionError(str(exc))

    def do_remove_artifact(
        self, artifact: Artifact, *, user: User | None = None
    ) -> None:
        """Remove the artifact from the collection."""
        CollectionItem.active_objects.filter(
            artifact=artifact, parent_collection=self.collection
        ).update(removed_by_user=user, removed_at=timezone.now())
