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

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

import datetime as dt
from typing import Any, override

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, WorkRequest
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

    @override
    def do_add_bare_data(
        self,
        category: BareDataCategory,
        *,
        user: User,
        workflow: WorkRequest | None = None,
        data: dict[str, Any] | None = None,  # noqa: U100
        name: str | None = None,
        replace: bool = False,
        created_at: dt.datetime | None = None,
        replaced_by: CollectionItem | None = None,
    ) -> CollectionItem:
        if name is None:
            raise ItemAdditionError(
                f"Adding to {CollectionCategory.WORKFLOW_INTERNAL} requires "
                f"an item name"
            )

        if data is not None and category != BareDataCategory.PROMISE:
            # Only PROMISE can have data keys starting with promise_
            for key in data:
                if key.startswith("promise_"):
                    raise ItemAdditionError(
                        f'Fields starting with "promise_" are not '
                        f'allowed for category "{category}"'
                    )

        if replace:
            self.remove_items_by_name(
                name=name,
                child_types=[CollectionItem.Types.BARE],
                user=user,
                workflow=workflow,
            )

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

    @override
    def do_add_artifact(
        self,
        artifact: Artifact,
        *,
        user: User,
        workflow: WorkRequest | None = None,
        variables: dict[str, Any] | None = None,
        name: str | None = None,
        replace: bool = False,
        created_at: dt.datetime | None = None,
        replaced_by: CollectionItem | None = None,
    ) -> CollectionItem:
        """
        Add the artifact into the managed collection.

        If replace=True the artifact can replace an artifact or bare data.
        """
        if name is None:
            raise ItemAdditionError(
                f"Adding to {CollectionCategory.WORKFLOW_INTERNAL} requires "
                f"an item name"
            )

        if replace:
            self.remove_items_by_name(
                name=name,
                child_types=[
                    CollectionItem.Types.ARTIFACT,
                    CollectionItem.Types.BARE,
                ],
                user=user,
                workflow=workflow,
            )

        if variables is not None:
            for key in variables:
                # Forbid names starting with 'promise_'
                if key.startswith("promise_"):
                    raise ItemAdditionError(
                        'Keys starting with "promise_" '
                        'are not allowed in variables'
                    )
        try:
            return CollectionItem.objects.create_from_artifact(
                artifact,
                parent_collection=self.collection,
                name=name,
                data=variables or {},
                created_at=created_at,
                created_by_user=user,
                created_by_workflow=workflow,
                replaced_by=replaced_by,
            )
        except IntegrityError as exc:
            raise ItemAdditionError(str(exc))

    @override
    def do_remove_item(
        self,
        item: CollectionItem,
        *,
        user: User | None = None,
        workflow: WorkRequest | None = None,
    ) -> None:
        item.removed_by_user = user
        item.removed_by_workflow = workflow
        item.removed_at = timezone.now()
        item.save()
