"""Functionalities for serialization and deserialization."""

import logging
import struct
import typing

if typing.TYPE_CHECKING:
    from .types import Packet

import google.protobuf.message

from .constants import MAX_PAYLOAD_SIZE

_logger = logging.getLogger(__name__)


def serialize(packet: Packet | None) -> bytes:
    """Serialize a packet into bytes for transmission.

    Parameters
    ----------
    packet
        Packet to serialize.

    Returns
    -------
    bytes
        Serialized packet.

    Raises
    ------
    TypeError
        If the packet type is unsupported.
    """
    if packet is None:
        return b""

    if isinstance(packet, bytes):
        return packet

    if isinstance(packet, str):
        return packet.encode("utf-8")

    if isinstance(packet, int):
        return packet.to_bytes()

    if isinstance(packet, float):
        return struct.pack("f", packet)

    if isinstance(packet, google.protobuf.message.Message):
        return packet.SerializeToString()

    details = f"Unsupported packet type: {type(packet).__name__}"  # type: ignore[unreachable]
    _logger.error(details)
    raise TypeError(details)


@typing.overload
def deserialize(payload: bytes, packet_type: type[bytes]) -> bytes: ...


@typing.overload
def deserialize(payload: bytes, packet_type: type[str]) -> str: ...


@typing.overload
def deserialize(payload: bytes, packet_type: type[int]) -> int: ...


@typing.overload
def deserialize(payload: bytes, packet_type: type[float]) -> float: ...


@typing.overload
def deserialize[T: google.protobuf.message.Message](
    payload: bytes,
    packet_type: type[T],
) -> T: ...


def deserialize(payload: bytes, packet_type: typing.Any):  # type: ignore[no-untyped-def]
    """Deserialize bytes into a packet.

    Parameters
    ----------
    payload
        Raw bytes to deserialize.
    packet_type
        Target type to deserialize into.

    Returns
    -------
    typing.Any
        Deserialized payload.

    Raises
    ------
    ValueError
        If the protobuf deserialization does not consume all bytes.
    TypeError
        If the packet type is unsupported.
    """
    _validate_payload_size(payload, str(packet_type))

    if packet_type is bytes:
        return payload

    if packet_type is str:
        return payload.decode("utf-8")

    if packet_type is int:
        return int.from_bytes(payload)

    if packet_type is float:
        return typing.cast("float", struct.unpack("f", payload)[0])

    if issubclass(packet_type, google.protobuf.message.Message):
        message = packet_type()
        bytes_read = message.ParseFromString(payload)
        if bytes_read != len(payload):
            error_message = (
                f"Protobuf deserialization consumed {bytes_read} of "
                f"{len(payload)} bytes"
            )
            raise ValueError(error_message)
        return message

    details = f"Unsupported packet type: {packet_type.__name__}"
    _logger.error(details)
    raise TypeError(details)


def _validate_payload_size(payload: bytes, context: str) -> None:
    """Guard against oversized payloads.

    Parameters
    ----------
    payload
        Payload to validate.
    context
        Description of the context for error messages.

    Raises
    ------
    ValueError
        If the payload exceeds the maximum allowed size.
    """
    if len(payload) > MAX_PAYLOAD_SIZE:
        details = (
            f"Payload too large for {context}: {len(payload)} bytes  "
            f"(max {MAX_PAYLOAD_SIZE})"
        )
        _logger.error(details)
        raise ValueError(details)
