import json
import pickle
from abc import ABC, abstractmethod
from typing import Any


class Serializer(ABC):
    """Abstract serializer for transparent value encoding / decoding."""

    @abstractmethod
    def serialize(self, value: Any) -> Any:
        """Encode *value* for storage (returned value is stored directly)."""
        ...

    @abstractmethod
    def deserialize(self, value: Any) -> Any:
        """Decode *value* from storage back to the original Python object."""
        ...


# ---------------------------------------------------------------------------
# Smart (JSON) serializer — the default
# ---------------------------------------------------------------------------

_PRIMITIVE_TYPES = (str, int, float, bool, type(None))


class SmartSerializer(Serializer):
    """Auto-detecting serializer.

    * Primitives (``str``, ``int``, ``float``, ``bool``, ``None``) pass
      through as-is.
    * Complex types (``dict``, ``list``, ``set``, ``tuple``) are JSON-encoded
      with :func:`json.dumps`.

    On deserialization the value is first tried as JSON; if that fails the
    raw string is returned (or coerced to int/float when possible).
    """

    def serialize(self, value: Any) -> Any:
        if isinstance(value, _PRIMITIVE_TYPES):
            return value
        return json.dumps(value, default=str)

    def deserialize(self, value: Any) -> Any:
        # Bytes → string (Redis with decode_responses=False case)
        if isinstance(value, bytes):
            value = value.decode("utf-8")

        # Already a non-string primitive — return as-is
        if not isinstance(value, str):
            return value

        # Try JSON first — handles complex types AND JSON-native primitives
        # (e.g. 42 → 42, true → True, "hello" → "hello")
        try:
            return json.loads(value)
        except (json.JSONDecodeError, TypeError, ValueError):
            pass

        # Try numeric coercion for plain strings like "42"
        try:
            if value.isdigit() or (value.startswith("-") and value[1:].isdigit()):
                return int(value)
        except (ValueError, AttributeError):
            pass

        try:
            return float(value)
        except (ValueError, TypeError):
            pass

        # Fallback — return the raw string
        return value


# ---------------------------------------------------------------------------
# Pickle serializer
# ---------------------------------------------------------------------------


class PickleSerializer(Serializer):
    """Full Python-object serialization via :mod:`pickle`.

    Preserves exact Python types but is Python-only and not human-readable.
    """

    def serialize(self, value: Any) -> bytes:
        return pickle.dumps(value)

    def deserialize(self, value: Any) -> Any:
        if isinstance(value, str):
            value = value.encode("latin-1")
        return pickle.loads(value)


# ---------------------------------------------------------------------------
# Passthrough (no-op) serializer
# ---------------------------------------------------------------------------


class PassthroughSerializer(Serializer):
    """No-op serializer — values are stored and returned as-is.

    The caller is responsible for encoding / decoding.
    """

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

    def deserialize(self, value: Any) -> Any:
        return value
