# 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.
"""Serializers for task inputs."""

import abc
from typing import Any, override

import pydantic

from debusine.tasks.models import (
    ExtraExternalRepository,
    InputArtifactMultiple,
    InputCollectionSingle,
)


class TaskInputSerializer[ValueType](abc.ABC):
    """Serialize/deserialize a TaskInfo value."""

    @abc.abstractmethod
    def serialize(self, value: ValueType) -> Any:
        """Serialize a value to a structure for JSON."""

    @abc.abstractmethod
    def deserialize(self, value: Any) -> ValueType:
        """Deserialize a value from a structure from JSON."""


class ScalarSerializer[ValueType](TaskInputSerializer[ValueType]):
    """Serialize/deserialize a scalar value that already fits in JSON."""

    def __init__(self, value_type: type[ValueType]) -> None:
        """Store the type used to validate serialized values."""
        self.value_type = value_type

    @override
    def serialize(self, value: ValueType) -> Any:
        return value

    @override
    def deserialize(self, value: Any) -> ValueType:
        if not isinstance(value, self.value_type):
            raise ValueError(
                f"Value {value!r} is not an instance of {self.value_type}"
            )
        return value


class PydanticSerializer[ValueType: pydantic.BaseModel](
    TaskInputSerializer[ValueType]
):
    """Serialize/deserialize a pydantic model."""

    def __init__(self, value_type: type[ValueType]) -> None:
        """Store the Pydantic model used to validate serialized values."""
        self.value_type = value_type

    @override
    def serialize(self, value: ValueType) -> Any:
        return value.model_dump(mode="json")

    @override
    def deserialize(self, value: Any) -> ValueType:
        return self.value_type.model_validate(value)


class OptionalPydanticSerializer[ValueType: pydantic.BaseModel](
    TaskInputSerializer[ValueType | None]
):
    """Serialize/deserialize a pydantic model."""

    def __init__(self, value_type: type[ValueType]) -> None:
        """Store the Pydantic model used to validate serialized values."""
        self.value_type = value_type

    @override
    def serialize(self, value: ValueType | None) -> Any:
        if value is None:
            return None
        return value.model_dump(mode="json")

    @override
    def deserialize(self, value: Any) -> ValueType | None:
        if value is None:
            return None
        return self.value_type.model_validate(value)


class PydanticListSerializer[ItemType: pydantic.BaseModel](
    TaskInputSerializer[list[ItemType]]
):
    """Serialize/deserialize a list of pydantic models."""

    def __init__(self, item_type: type[ItemType]) -> None:
        """Store the Pydantic model used to validate serialized values."""
        self.item_type = item_type

    @override
    def serialize(self, value: list[ItemType]) -> Any:
        return [item.model_dump(mode="json") for item in value]

    @override
    def deserialize(self, value: Any) -> list[ItemType]:
        if not isinstance(value, list):
            raise ValueError(f"Value {value!r} is not an instance of {list}")
        return [self.item_type.model_validate(item) for item in value]


class MultiInputListSerializer(
    TaskInputSerializer[list[InputArtifactMultiple]]
):
    """Serialize/deserialize a list of InputArtifactMultiple."""

    @override
    def serialize(self, value: list[InputArtifactMultiple]) -> Any:
        return [item.model_dump(mode="json") for item in value]

    @override
    def deserialize(self, value: Any) -> list[InputArtifactMultiple]:
        if not isinstance(value, list):
            raise ValueError(f"Value {value!r} is not an instance of {list}")

        res: list[InputArtifactMultiple] = [
            InputArtifactMultiple.model_validate(item) for item in value
        ]
        return res


class SuiteArchiveInputSerializer(
    TaskInputSerializer[
        tuple[InputCollectionSingle, InputCollectionSingle | None]
    ]
):
    """Serialize a suite, archive tuple."""

    @override
    def serialize(
        self, value: tuple[InputCollectionSingle, InputCollectionSingle | None]
    ) -> Any:
        return [
            value[0].model_dump(mode="json"),
            (
                value[1].model_dump(mode="json")
                if value[1] is not None
                else None
            ),
        ]

    @override
    def deserialize(
        self, value: Any
    ) -> tuple[InputCollectionSingle, InputCollectionSingle | None]:
        if not isinstance(value, list):
            raise ValueError(f"Value {value!r} is not an instance of {list}")
        if not len(value) == 2:
            raise ValueError(
                f"Value {value!r} should be a list of exactly two items"
            )
        return (
            InputCollectionSingle.model_validate(value[0]),
            (
                InputCollectionSingle.model_validate(value[1])
                if value[1] is not None
                else None
            ),
        )


class ExtraRepositoriesSerializer(
    TaskInputSerializer[list[ExtraExternalRepository] | None]
):
    """Serialize a list of extra repositories."""

    @override
    def serialize(
        self, value: list[ExtraExternalRepository] | None
    ) -> list[dict[str, Any]] | None:
        if value is None:
            return None
        return [item.model_dump(mode="json") for item in value]

    @override
    def deserialize(self, value: Any) -> list[ExtraExternalRepository] | None:
        if value is None:
            return None
        return [ExtraExternalRepository.model_validate(item) for item in value]
