# 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.
"""Scheduler tag support."""

from collections.abc import Collection
from typing import Any

from debusine.utils.tag_utils import (
    CandidateTags,
    DerivationRules,
    ProvenanceRestrictions,
)


class Tagset:
    """Build a set of tags."""

    def __init__(self) -> None:
        """Initialize an empty tag set."""
        # TODO: allowing arbitrary places to provide provenance restrictions
        # for *required* tag sets is equivalent to allowing those places to
        # remove tags, which is likely a security issue

        #: List of provenance restrictions to apply
        self.restrictions: list[ProvenanceRestrictions] = []
        self.derivation_rules: list[DerivationRules] = []
        #: Set of tags being built
        self.tags: set[str] = set()
        #: Log of interventions applied to merge operations
        self.log: list[str] = []

    def add(self, provenance: str, tags: Collection[str] | None) -> None:
        """
        Add tags from a given provenance to the tag set.

        Provenance restrictions are applied, and if a tag or its prefix has a
        provenance allow list that applies, it is enforced.

        Multiple matching allowlists all count, so the tag is only added if its
        provenance is in all relevant allowlists.
        """
        filtered_tags: set[str] = set(tags) if tags else set()
        for restriction in self.restrictions:
            t = restriction.filter_set(provenance, filtered_tags)
            if t != filtered_tags:
                removed = filtered_tags - t
                self.log.append(
                    f"{restriction.name} provenance restrictions"
                    f" forbids tags {" ".join(sorted(removed))}",
                )
            filtered_tags = t
        self.tags.update(filtered_tags)

    def add_candidates(self, candidates: CandidateTags[Any]) -> None:
        """Add tags from the given sets of candidate tags."""
        for provenance, tags in candidates.sets.items():
            self.add(provenance, tags)

    def finalize(self) -> None:
        """Finalize the tag set, applying derivation rules."""
        for rules in self.derivation_rules:
            new_tags = rules.compute(self.tags)
            if new_tags:
                self.log.append(
                    f"{rules.provenance} derivation rules adds tags"
                    f" {", ".join(sorted(new_tags))}"
                )
            self.add(rules.provenance, new_tags)


def make_task_provided_tagset() -> Tagset:
    """Create a new Tagset configured for task-provided tags."""
    import debusine.tasks.tags as ttags

    tagset = Tagset()
    tagset.restrictions.append(ttags.restrictions_when_provided)
    # TODO: add restrictions from django settings?
    # TODO: configure inferences
    return tagset


def make_task_required_tagset() -> Tagset:
    """Create a new Tagset configured for task-required tags."""
    import debusine.worker.tags as wtags

    tagset = Tagset()
    tagset.restrictions.append(wtags.restrictions_when_required)
    return tagset


def make_worker_provided_tagset() -> Tagset:
    """Create a new Tagset configured for worker-provided tags."""
    import debusine.worker.tags as wtags

    tagset = Tagset()
    tagset.restrictions.append(wtags.restrictions_when_provided)
    # TODO: add restrictions from django settings?
    # TODO: configure inferences
    return tagset


def make_worker_required_tagset() -> Tagset:
    """Create a new Tagset configured for worker-required tags."""
    import debusine.tasks.tags as ttags

    tagset = Tagset()
    tagset.restrictions.append(ttags.restrictions_when_required)
    return tagset
