import json
import logging
import pickle
import typing as _t


class BaseSerializer:
    """This is the base interface for all default serializers.

    BaseSerializer.load and BaseSerializer.dump will
    default to pickle.load and pickle.dump. This is currently
    used only by FileSystemCache which dumps/loads to/from a file stream.
    """

    def _warn(self, e: Exception) -> None:
        logging.warning(f"An exception has been raised during an operation: {e}")

    def dump(
        self, value: int, f: _t.IO[bytes], protocol: int = pickle.HIGHEST_PROTOCOL
    ) -> None:
        try:
            pickle.dump(value, f, protocol)
        except (pickle.PickleError, pickle.PicklingError) as e:
            self._warn(e)

    def load(self, f: _t.BinaryIO) -> _t.Any:
        try:
            data = pickle.load(f)
        except pickle.PickleError as e:
            self._warn(e)
            return None
        else:
            return data

    """BaseSerializer.loads and BaseSerializer.dumps
    work on top of pickle.loads and pickle.dumps. Dumping/loading
    strings and byte strings is the default for most cache types.
    """

    def dumps(
        self, value: _t.Any, protocol: int = pickle.HIGHEST_PROTOCOL
    ) -> bytes | None:
        try:
            serialized = pickle.dumps(value, protocol)
        except (pickle.PickleError, pickle.PicklingError) as e:
            self._warn(e)
            return None
        return serialized

    def loads(self, bvalue: bytes) -> _t.Any:
        try:
            data = pickle.loads(bvalue)
        except pickle.PickleError as e:
            self._warn(e)
            return None
        else:
            return data


class BaseRedisSerializer(BaseSerializer):
    """Base serializer for Redis compatible caches."""

    def dumps(self, value: _t.Any, protocol: int = pickle.HIGHEST_PROTOCOL) -> bytes:
        """Dumps an object into a string for redis. Integers are stored as
        plain ASCII so they remain compatible with the native
        ``INCRBY``/``DECRBY`` operations used by :meth:`inc`/:meth:`dec`;
        everything else is pickled.
        """
        # check type instead of isinstance since bool is an int subclass
        if type(value) is int:
            return str(value).encode("ascii")
        return b"!" + pickle.dumps(value, protocol)

    def loads(self, value: bytes | None) -> _t.Any:
        """The reversal of :meth:`dumps`. This might be called with
        None.
        """
        if value is None:
            return None
        if value.startswith(b"!"):
            try:
                return pickle.loads(value[1:])
            except pickle.PickleError:
                return None
        try:
            return int(value)
        except ValueError:
            # before 0.8 we did not have serialization. Still support that.
            return value


class JSONSerializer(BaseSerializer):
    """Generic JSON serializer for caches.

    Use this serializer if you want to serialize to JSON instead of pickle.
    Note that JSON does not support serialization of Python bytes objects.
    If you need to cache bytes, use the default pickle-based serializer.
    This serializer is not used by default.
    """

    def dump(
        self, value: _t.Any, f: _t.IO[bytes], *args: _t.Any, **kwargs: _t.Any
    ) -> None:
        try:
            f.write(json.dumps(value, *args, **kwargs).encode())
        except (TypeError, ValueError) as e:
            self._warn(e)

    def load(self, f: _t.IO[bytes], *args: _t.Any, **kwargs: _t.Any) -> _t.Any:
        try:
            data = json.loads(f.read(), *args, **kwargs)
        except (TypeError, ValueError) as e:
            self._warn(e)
            return None
        else:
            return data

    def dumps(self, value: _t.Any, *args: _t.Any, **kwargs: _t.Any) -> bytes | None:
        try:
            serialized = json.dumps(value, *args, **kwargs).encode()
        except (TypeError, ValueError) as e:
            self._warn(e)
            return None
        return serialized

    def loads(
        self, bvalue: str | bytes | bytearray, *args: _t.Any, **kwargs: _t.Any
    ) -> _t.Any:
        try:
            data = json.loads(bvalue, *args, **kwargs)
        except (TypeError, ValueError) as e:
            self._warn(e)
            return None
        else:
            return data


"""Default serializers for each cache type.

The following classes can be used to further customize
serialiation behaviour. Alternatively, any serializer can be
overridden in order to use a custom serializer with a different
strategy altogether.
"""


class UWSGISerializer(BaseSerializer):
    """Default serializer for UWSGICache."""


class SimpleSerializer(BaseSerializer):
    """Default serializer for SimpleCache."""


class FileSystemSerializer(BaseSerializer):
    """Default serializer for FileSystemCache."""


class MongoDbSerializer(BaseSerializer):
    """Default serializer for MongoDbCache."""


class RedisSerializer(BaseRedisSerializer):
    """Default serializer for RedisCache."""


class ValkeySerializer(BaseRedisSerializer):
    """Default serializer for ValkeyCache."""


class DynamoDbSerializer(BaseRedisSerializer):
    """Default serializer for DynamoDbCache."""
