# 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 task input field serializers."""

from debusine.artifacts.models import (
    ArtifactCategory,
    CollectionCategory,
    DebianBinaryPackage,
)
from debusine.tasks.inputs.serializers import (
    ExtraRepositoriesSerializer,
    MultiInputListSerializer,
    OptionalPydanticSerializer,
    PydanticListSerializer,
    PydanticSerializer,
    ScalarSerializer,
    SuiteArchiveInputSerializer,
)
from debusine.tasks.models import (
    BaseTaskInputArtifact,
    ExtraExternalRepository,
    InputArtifactMultiple,
    InputCollectionSingle,
    LookupMultiple,
)
from debusine.test import TestCase


class SerializerTestCase(TestCase):
    """Base test case for serializer tests."""

    def make_multi(
        self,
        *artifacts: BaseTaskInputArtifact,
        label: str = "label",
        lookup: LookupMultiple = LookupMultiple(("lookup",)),
    ) -> InputArtifactMultiple:
        """Create an InputArtifactMultiple instance."""
        return InputArtifactMultiple(
            label=label, lookup=lookup, artifacts=list(artifacts)
        )


class ScalarSerializerTests(SerializerTestCase):
    """Tests for :py:class:`ScalarSerializer`."""

    def test_serialization(self) -> None:
        serializer = ScalarSerializer(int)
        self.assertEqual(serializer.serialize(3), 3)
        self.assertEqual(serializer.deserialize(3), 3)

    def test_invalid_value(self) -> None:
        serializer = ScalarSerializer(int)
        with self.assertRaisesRegex(
            ValueError, r"Value 'foo' is not an instance of <class 'int'>"
        ):
            serializer.deserialize("foo")


class PydanticSerializerTests(SerializerTestCase):
    """Tests for :py:class:`PydanticSerializer`."""

    sample = DebianBinaryPackage(
        srcpkg_name="unsigned",
        srcpkg_version="1.0",
        deb_fields={
            "Package": "hello",
            "Version": "1.0-1",
            "Architecture": "amd64",
        },
        deb_control_files=[],
    )

    def test_serialization(self) -> None:
        serializer = PydanticSerializer(DebianBinaryPackage)
        serialized = serializer.serialize(self.sample)
        assert isinstance(serialized, dict)
        deserialized = serializer.deserialize(serialized)
        self.assertEqual(deserialized, self.sample)


class OptionalPydanticSerializerTests(SerializerTestCase):
    """Tests for :py:class:`OptionalPydanticSerializer`."""

    sample = DebianBinaryPackage(
        srcpkg_name="unsigned",
        srcpkg_version="1.0",
        deb_fields={
            "Package": "hello",
            "Version": "1.0-1",
            "Architecture": "amd64",
        },
        deb_control_files=[],
    )

    def test_serialization(self) -> None:
        serializer = OptionalPydanticSerializer(DebianBinaryPackage)
        serialized = serializer.serialize(self.sample)
        assert isinstance(serialized, dict)
        deserialized = serializer.deserialize(serialized)
        self.assertEqual(deserialized, self.sample)

    def test_serialize_none(self) -> None:
        serializer = OptionalPydanticSerializer(DebianBinaryPackage)
        serialized = serializer.serialize(None)
        self.assertIsNone(serialized)
        deserialized = serializer.deserialize(None)
        self.assertIsNone(deserialized)


class PydanticListSerializerTests(SerializerTestCase):
    """Tests for :py:class:`PydanticSerializer`."""

    sample = DebianBinaryPackage(
        srcpkg_name="unsigned",
        srcpkg_version="1.0",
        deb_fields={
            "Package": "hello",
            "Version": "1.0-1",
            "Architecture": "amd64",
        },
        deb_control_files=[],
    )

    def test_serialization(self) -> None:
        serializer = PydanticListSerializer(DebianBinaryPackage)
        serialized = serializer.serialize([self.sample])
        assert isinstance(serialized, list)
        for idx, item in enumerate(serialized):
            with self.subTest(idx=idx):
                assert isinstance(item, dict)
        deserialized = serializer.deserialize(serialized)
        self.assertEqual(deserialized, [self.sample])

    def test_deserialize_invalid_type(self) -> None:
        serializer = PydanticListSerializer(DebianBinaryPackage)
        with self.assertRaisesRegex(
            ValueError, r"Value 'foo' is not an instance of <class 'list'>"
        ):
            serializer.deserialize("foo")


class MultiInputListSerializerTests(SerializerTestCase):
    """Tests for :py:class:`MultiInputListSerializer`."""

    sample = BaseTaskInputArtifact(
        artifact_id=1,
        category=ArtifactCategory.BINARY_PACKAGE,
        data=DebianBinaryPackage(
            srcpkg_name="unsigned",
            srcpkg_version="1.0",
            deb_fields={
                "Package": "hello",
                "Version": "1.0-1",
                "Architecture": "amd64",
            },
            deb_control_files=[],
        ),
    )

    def test_serialization_list(self) -> None:
        sample = [
            self.make_multi(self.sample, self.sample),
            self.make_multi(self.sample),
        ]
        serializer = MultiInputListSerializer()
        serialized = serializer.serialize(sample)
        assert isinstance(serialized, list)
        assert isinstance(serialized[0], dict)
        deserialized = serializer.deserialize(serialized)
        self.assertEqual(deserialized, sample)

    def test_deserialize_invalid_type(self) -> None:
        serializer = MultiInputListSerializer()
        with self.assertRaisesRegex(
            ValueError, r"Value 'foo' is not an instance of <class 'list'>"
        ):
            serializer.deserialize("foo")

    def test_deserialize_invalid_list_item_type(self) -> None:
        serializer = MultiInputListSerializer()
        with self.assertRaisesRegex(
            ValueError,
            "Input should be a valid dictionary"
            " or instance of InputArtifactMultiple",
        ):
            serializer.deserialize(["foo"])


class SuiteArchiveInputSerializerTests(SerializerTestCase):
    """Tests for :py:class:`SuiteArchiveInputSerializer`."""

    suite = InputCollectionSingle(
        label="field",
        lookup="lookup",
        collection_id=1,
        scope_name="scope",
        workspace_name="workspace",
        category=CollectionCategory.SUITE,
        name="sid",
        data={},
    )
    archive = InputCollectionSingle(
        label="field",
        lookup="lookup",
        collection_id=2,
        scope_name="scope",
        workspace_name="workspace",
        category=CollectionCategory.ARCHIVE,
        name="_",
        data={},
    )

    def test_serialization_both_set(self) -> None:
        sample = (self.suite, self.archive)
        serializer = SuiteArchiveInputSerializer()
        serialized = serializer.serialize(sample)
        assert isinstance(serialized, list)
        deserialized = serializer.deserialize(serialized)
        self.assertEqual(deserialized, sample)

    def test_serialization_archive_unset(self) -> None:
        sample = (self.suite, None)
        serializer = SuiteArchiveInputSerializer()
        serialized = serializer.serialize(sample)
        assert isinstance(serialized, list)
        deserialized = serializer.deserialize(serialized)
        self.assertEqual(deserialized, sample)

    def test_deserialize_invalid_type(self) -> None:
        serializer = SuiteArchiveInputSerializer()
        with self.assertRaisesRegex(
            ValueError, r"Value 'foo' is not an instance of <class 'list'>"
        ):
            serializer.deserialize("foo")

    def test_deserialize_invalid_length(self) -> None:
        serializer = SuiteArchiveInputSerializer()
        with self.assertRaisesRegex(
            ValueError, r"Value \[\] should be a list of exactly two items"
        ):
            serializer.deserialize([])


class ExtraRepositoriesSerializerTests(SerializerTestCase):
    """Tests for :py:class:`ExtraRepositoriesSerializer`."""

    sample = ExtraExternalRepository(
        url="https://example.org", suite="sid", components=["main"]
    )

    def test_serialization_list(self) -> None:
        serializer = ExtraRepositoriesSerializer()
        serialized = serializer.serialize([self.sample, self.sample])
        assert isinstance(serialized, list)
        deserialized = serializer.deserialize(serialized)
        self.assertEqual(deserialized, [self.sample, self.sample])

    def test_serialization_none(self) -> None:
        serializer = ExtraRepositoriesSerializer()
        serialized = serializer.serialize(None)
        self.assertIsNone(serialized)
        deserialized = serializer.deserialize(None)
        self.assertIsNone(deserialized)
