"""
Пакетный протокол SDR Rover Link.

Модуль содержит функции:
- build_packet() — формирование пакета;
- parse_packet() — проверка и разбор пакета.

Версия протокола: 0.1
"""

from dataclasses import dataclass
import struct
import zlib


# ============================================================
# Константы протокола
# ============================================================

SYNC_WORD = 0xAA55
PROTOCOL_VERSION = 1

MESSAGE_TYPE_TEXT = 1
MESSAGE_TYPE_TELEMETRY = 2
MESSAGE_TYPE_IMAGE_FRAGMENT = 3
MESSAGE_TYPE_ACK = 4

# Структура заголовка:
#
# H — SYNC, 2 байта
# B — версия, 1 байт
# B — тип сообщения, 1 байт
# H — порядковый номер, 2 байта
# H — длина полезной нагрузки, 2 байта
HEADER_FORMAT = ">HBBHH"

# CRC-32 хранится как беззнаковое 32-битное число.
CRC_FORMAT = ">I"

HEADER_SIZE = struct.calcsize(HEADER_FORMAT)
CRC_SIZE = struct.calcsize(CRC_FORMAT)

MAX_PAYLOAD_SIZE = 65535


# ============================================================
# Ошибки протокола
# ============================================================

class PacketError(ValueError):
    """Общая ошибка структуры пакета."""


class CRCError(PacketError):
    """Ошибка контрольной суммы CRC."""


# ============================================================
# Результат разбора пакета
# ============================================================

@dataclass(frozen=True)
class ParsedPacket:
    """
    Представление успешно принятого пакета.

    Атрибуты:
        version: версия протокола;
        message_type: тип сообщения;
        sequence_number: порядковый номер;
        payload: полезная нагрузка в байтах.
    """

    version: int
    message_type: int
    sequence_number: int
    payload: bytes


# ============================================================
# Формирование пакета
# ============================================================

def build_packet(
    payload: bytes,
    message_type: int,
    sequence_number: int,
) -> bytes:
    """
    Сформировать пакет SDR Rover Link.

    Параметры:
        payload:
            Полезная нагрузка в байтах.

        message_type:
            Тип сообщения от 0 до 255.

        sequence_number:
            Порядковый номер от 0 до 65535.

    Возвращает:
        Полный пакет в виде bytes.
    """

    if not isinstance(payload, (bytes, bytearray)):
        raise TypeError(
            "payload должен иметь тип bytes или bytearray"
        )

    payload = bytes(payload)

    if not 0 <= message_type <= 255:
        raise ValueError(
            "message_type должен находиться в диапазоне 0...255"
        )

    if not 0 <= sequence_number <= 65535:
        raise ValueError(
            "sequence_number должен находиться в диапазоне 0...65535"
        )

    payload_length = len(payload)

    if payload_length > MAX_PAYLOAD_SIZE:
        raise ValueError(
            f"Полезная нагрузка превышает {MAX_PAYLOAD_SIZE} байт"
        )

    header = struct.pack(
        HEADER_FORMAT,
        SYNC_WORD,
        PROTOCOL_VERSION,
        message_type,
        sequence_number,
        payload_length,
    )

    packet_without_crc = header + payload

    crc_value = (
        zlib.crc32(packet_without_crc) & 0xFFFFFFFF
    )

    crc_bytes = struct.pack(
        CRC_FORMAT,
        crc_value,
    )

    return packet_without_crc + crc_bytes


# ============================================================
# Разбор пакета
# ============================================================

def parse_packet(packet: bytes) -> ParsedPacket:
    """
    Проверить и разобрать пакет SDR Rover Link.

    Проверяются:
        - минимальный размер;
        - маркер SYNC;
        - версия протокола;
        - длина пакета;
        - CRC-32.

    Возвращает:
        ParsedPacket.

    Вызывает:
        PacketError — при ошибке структуры;
        CRCError — при несовпадении CRC.
    """

    if not isinstance(packet, (bytes, bytearray)):
        raise TypeError(
            "packet должен иметь тип bytes или bytearray"
        )

    packet = bytes(packet)

    minimum_packet_size = HEADER_SIZE + CRC_SIZE

    if len(packet) < minimum_packet_size:
        raise PacketError(
            "Пакет слишком короткий"
        )

    header = packet[:HEADER_SIZE]

    (
        sync_word,
        version,
        message_type,
        sequence_number,
        payload_length,
    ) = struct.unpack(
        HEADER_FORMAT,
        header,
    )

    if sync_word != SYNC_WORD:
        raise PacketError(
            f"Неверный SYNC: 0x{sync_word:04X}"
        )

    if version != PROTOCOL_VERSION:
        raise PacketError(
            f"Неподдерживаемая версия протокола: {version}"
        )

    expected_packet_size = (
        HEADER_SIZE
        + payload_length
        + CRC_SIZE
    )

    if len(packet) != expected_packet_size:
        raise PacketError(
            "Длина пакета не соответствует полю LENGTH: "
            f"ожидалось {expected_packet_size} байт, "
            f"получено {len(packet)} байт"
        )

    data_without_crc = packet[:-CRC_SIZE]
    received_crc_bytes = packet[-CRC_SIZE:]

    (received_crc,) = struct.unpack(
        CRC_FORMAT,
        received_crc_bytes,
    )

    calculated_crc = (
        zlib.crc32(data_without_crc) & 0xFFFFFFFF
    )

    if received_crc != calculated_crc:
        raise CRCError(
            "CRC не совпадает: "
            f"получено 0x{received_crc:08X}, "
            f"вычислено 0x{calculated_crc:08X}"
        )

    payload = packet[
        HEADER_SIZE:-CRC_SIZE
    ]

    return ParsedPacket(
        version=version,
        message_type=message_type,
        sequence_number=sequence_number,
        payload=payload,
    )