# 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 scheduler tags backend functions."""

from typing import override
from unittest import mock

import debusine.worker.tags as wtags
from debusine.server.tags import Tagset, make_worker_provided_tagset
from debusine.test import TestCase
from debusine.utils.tag_utils import (
    CandidateTags,
    DerivationRules,
    ProvenanceRestrictions,
)


class TagsetTests(TestCase):
    """Tests for :py:class:`Tagset`."""

    def test_init(self) -> None:
        t = Tagset()
        self.assertEqual(t.restrictions, [])
        self.assertEqual(t.derivation_rules, [])
        self.assertEqual(t.tags, set())
        self.assertEqual(t.log, [])

    def test_plain(self) -> None:
        t = Tagset()
        t.add("test", None)
        t.add("test", ["foo"])
        t.add("test", {"bar", "baz"})
        t.finalize()
        self.assertEqual(t.tags, {"foo", "bar", "baz"})

    def test_add_candidates(self) -> None:
        c: CandidateTags[str] = CandidateTags()
        c.add("test", {"foo"})
        c.add("test1", {"bar", "baz"})

        t = Tagset()
        with self.spy_object(t, "add") as add:
            t.add_candidates(c)

        # Ensure that adding candidates is the same as calling add with their
        # contents
        add.assert_has_calls(
            [
                mock.call("test", {"foo"}),
                mock.call("test1", {"bar", "baz"}),
            ]
        )

    def test_restrictions(self) -> None:
        restrictions = ProvenanceRestrictions("test")
        restrictions.add_exact("group", ["system"])
        restrictions.add_exact("test", ["system"])
        t = Tagset()
        t.restrictions.append(restrictions)
        t.add("system", {"test", "foo"})
        t.add("workspace", {"group", "bar"})
        t.finalize()
        self.assertEqual(t.tags, {"test", "foo", "bar"})
        self.assertEqual(
            t.log, ["test provenance restrictions forbids tags group"]
        )

    def test_derivation(self) -> None:
        derivation = DerivationRules(provenance="system")
        derivation.add_rule("dm or dd", {"debian"})
        t = Tagset()
        t.derivation_rules.append(derivation)
        t.add("system", {"dm", "foo"})
        t.finalize()
        self.assertEqual(t.tags, {"dm", "debian", "foo"})
        self.assertEqual(t.log, ["system derivation rules adds tags debian"])

    def test_derivation_exists_but_does_not_match(self) -> None:
        derivation = DerivationRules(provenance="system")
        derivation.add_rule("dm or dd", {"debian"})
        t = Tagset()
        t.derivation_rules.append(derivation)
        t.add("system", {"foo"})
        t.finalize()
        self.assertEqual(t.tags, {"foo"})
        self.assertEqual(t.log, [])

    def test_derivation_restrictions(self) -> None:
        restrictions = ProvenanceRestrictions("test")
        restrictions.add_exact("group", ["system"])
        derivation = DerivationRules(provenance="workspace")
        derivation.add_rule("dm or dd", {"debian"})
        derivation.add_rule("foo", {"group"})
        t = Tagset()
        t.restrictions.append(restrictions)
        t.derivation_rules.append(derivation)
        t.add("system", {"test", "foo", "dm"})
        t.finalize()
        self.assertEqual(t.tags, {"test", "foo", "dm", "debian"})
        self.assertEqual(
            t.log,
            [
                "workspace derivation rules adds tags debian, group",
                "test provenance restrictions forbids tags group",
            ],
        )


class TestWorkerProvidedTags(TestCase):
    """Test worker provided tags."""

    @override
    def setUp(self) -> None:
        super().setUp()
        self.tagset = make_worker_provided_tagset()

    def test_class(self) -> None:
        self.tagset.add(
            wtags.ProvenanceProvided.WORKER, [wtags.CLASS_PREFIX + "worker"]
        )
        self.tagset.add(
            wtags.ProvenanceProvided.ADMIN, [wtags.CLASS_PREFIX + "admin"]
        )
        self.tagset.add(
            wtags.ProvenanceProvided.SYSTEM, [wtags.CLASS_PREFIX + "system"]
        )
        self.tagset.finalize()
        self.assertEqual(self.tagset.tags, {wtags.CLASS_PREFIX + "admin"})
