from __future__ import annotations

from typing import TypeVar

from google.protobuf.json_format import MessageToJson
from google.protobuf.json_format import Parse as MessageFromJson
from google.protobuf.message import Message

from connectrpc.codec import Codec

CODEC_NAME_PROTO = "proto"
CODEC_NAME_JSON = "json"


T_contra = TypeVar("T_contra", contravariant=True)
U = TypeVar("U")
V = TypeVar("V", bound=Message)


class ProtoBinaryCodec(Codec[Message, V]):
    """Codec for Protocol bytes | bytearrays binary format."""

    def name(self) -> str:
        return "proto"

    def encode(self, message: Message) -> bytes:
        return message.SerializeToString()

    def decode(self, data: bytes | bytearray, message_class: type[V]) -> V:
        return message_class.FromString(data)  # ty:ignore[invalid-argument-type] type is incorrect


class ProtoJSONCodec(Codec[Message, V]):
    """Codec for Protocol bytes | bytearrays JSON format."""

    def __init__(self, name: str = "json") -> None:
        self._name = name

    def name(self) -> str:
        return self._name

    def encode(self, message: Message) -> bytes:
        return MessageToJson(message).encode()

    def decode(self, data: bytes | bytearray, message_class: type[V]) -> V:
        message = message_class()
        MessageFromJson(data, message)  # ty:ignore[invalid-argument-type] type is incorrect
        return message


_proto_binary_codec = ProtoBinaryCodec()
_proto_json_codec = ProtoJSONCodec()
_default_codecs: list[Codec] = [_proto_binary_codec, _proto_json_codec]


def google_protobuf_codecs() -> list[Codec]:
    """Returns the codecs for marshaling Protocol Buffers using google.protobuf."""
    return _default_codecs


def google_protobuf_binary_codec() -> Codec:
    """Returns the Protocol Buffers binary codec using google.protobuf."""
    return _proto_binary_codec


def google_protobuf_json_codec() -> Codec:
    """Returns the Protocol Buffers JSON codec using google.protobuf."""
    return _proto_json_codec
