"""Packet frame encoding/decoding for LibreVNA protocol stream."""

from __future__ import annotations

import logging
import struct

from ..enums import PacketType
from ..exceptions import CRCError, ParseError
from ..models import Packet
from .crc32 import crc32

logger = logging.getLogger(__name__)

_HEADER = 0x5A
_FRAME_OVERHEAD = 8  # header + length + type + crc
_MAX_FRAME_LENGTH = 4096


_NO_CRC_PACKET_TYPES = {
    PacketType.VNA_DATAPOINT,
}


def encode_frame(packet: Packet) -> bytes:
    """Encode one packet into framed wire format."""
    payload = packet.payload
    if not isinstance(payload, (bytes, bytearray, memoryview)):
        raise TypeError("Packet payload must be bytes-like")

    payload_bytes = bytes(payload)
    length = _FRAME_OVERHEAD + len(payload_bytes)
    if length > 0xFFFF:
        raise ValueError("Packet is too large for protocol frame length field")

    frame = bytearray(length)
    frame[0] = _HEADER
    struct.pack_into("<H", frame, 1, length)
    frame[3] = int(packet.type)
    frame[4 : 4 + len(payload_bytes)] = payload_bytes

    crc_value = 0
    if packet.type not in _NO_CRC_PACKET_TYPES:
        crc_value = crc32(frame[:-4])
    struct.pack_into("<I", frame, length - 4, crc_value)
    return bytes(frame)


def decode_frame(frame: bytes) -> Packet:
    """Decode and validate one complete frame."""
    if len(frame) < _FRAME_OVERHEAD:
        raise ParseError("Frame is too short")
    if frame[0] != _HEADER:
        raise ParseError("Invalid frame header")

    (length,) = struct.unpack_from("<H", frame, 1)
    if length != len(frame):
        raise ParseError(f"Frame length mismatch: declared {length}, got {len(frame)}")

    packet_type_raw = frame[3]
    try:
        packet_type = PacketType(packet_type_raw)
    except ValueError as exc:
        raise ParseError(f"Unknown packet type id {packet_type_raw}") from exc

    (received_crc,) = struct.unpack_from("<I", frame, length - 4)
    if packet_type in _NO_CRC_PACKET_TYPES:
        if received_crc != 0:
            raise CRCError("VNADatapoint packet must carry zero CRC")
    else:
        computed_crc = crc32(frame[:-4])
        if received_crc != computed_crc:
            raise CRCError(
                f"CRC mismatch for packet {packet_type.name}: "
                f"received 0x{received_crc:08X}, computed 0x{computed_crc:08X}"
            )

    return Packet(type=packet_type, payload=frame[4:-4])


class FrameScanner:
    """Incremental frame scanner for raw USB byte streams."""

    def __init__(self) -> None:
        """Initialize internal undecoded byte buffer."""
        self._buffer = bytearray()

    def clear(self) -> None:
        """Drop all buffered undecoded bytes."""
        self._buffer.clear()

    def feed(self, chunk: bytes) -> list[Packet]:
        """Feed raw bytes and return every fully decoded packet."""
        if not chunk:
            return []
        self._buffer.extend(chunk)

        decoded: list[Packet] = []
        while True:
            header_index = self._buffer.find(_HEADER)
            if header_index < 0:
                self._buffer.clear()
                break
            if header_index > 0:
                del self._buffer[:header_index]

            if len(self._buffer) < 4:
                break

            (length,) = struct.unpack_from("<H", self._buffer, 1)
            if length < _FRAME_OVERHEAD or length > _MAX_FRAME_LENGTH:
                logger.debug("Discarding byte due to invalid frame length=%d", length)
                del self._buffer[0]
                continue

            if len(self._buffer) < length:
                break

            frame = bytes(self._buffer[:length])
            del self._buffer[:length]
            decoded.append(decode_frame(frame))

        return decoded
