"""Decode Spectra websocket replay payloads.

Spectra historically sent zstd-compressed JSON bytes. Newer frames may be
zstd-compressed msgpack bytes. Keep the format check on the decompressed bytes
so both rollout states are accepted without guessing from websocket frame type.
"""
from __future__ import annotations

import json
from typing import Any

import msgpack
import zstandard as zstd

_ZSTD_DECOMPRESSOR = zstd.ZstdDecompressor()
_MAX_OUTPUT_SIZE = 64 * 1024 * 1024


class SpectraPayloadError(ValueError):
    """Raised when a Spectra websocket payload cannot be decoded."""


def _frame_bytes(message: str | bytes | bytearray | memoryview) -> bytes:
    if isinstance(message, str):
        return message.encode("utf-8")
    return bytes(message)


def _decompress_if_needed(raw: bytes) -> bytes:
    try:
        return _ZSTD_DECOMPRESSOR.decompress(raw, max_output_size=_MAX_OUTPUT_SIZE)
    except zstd.ZstdError:
        return raw


def _first_non_ws_byte(payload: bytes) -> int | None:
    for byte in payload:
        if byte not in b" \t\r\n":
            return byte
    return None


def _looks_like_json(payload: bytes) -> bool:
    first = _first_non_ws_byte(payload)
    return first in (ord("{"), ord("["))


def _looks_like_msgpack_container(payload: bytes) -> bool:
    first = _first_non_ws_byte(payload)
    if first is None:
        return False
    return (
        0x80 <= first <= 0x9F  # fixmap/fixarray
        or first in (0xDC, 0xDD, 0xDE, 0xDF)  # array16/32, map16/32
    )


def decode_spectra_ws_payload(message: str | bytes | bytearray | memoryview) -> Any:
    """Decode a Spectra WS frame as zstd-json, zstd-msgpack, or plain JSON.

    Returns the decoded Python object. Raises ``SpectraPayloadError`` when the
    decompressed payload is neither a JSON object/array nor a msgpack map/array.
    """
    payload = _decompress_if_needed(_frame_bytes(message))

    if _looks_like_json(payload):
        try:
            return json.loads(payload.decode("utf-8"))
        except (UnicodeDecodeError, json.JSONDecodeError) as exc:
            raise SpectraPayloadError("Spectra WS payload looked like JSON but was invalid") from exc

    if _looks_like_msgpack_container(payload):
        try:
            return msgpack.unpackb(payload, raw=False)
        except (msgpack.ExtraData, msgpack.FormatError, msgpack.StackError, ValueError) as exc:
            raise SpectraPayloadError("Spectra WS payload looked like msgpack but was invalid") from exc

    raise SpectraPayloadError("Spectra WS payload was neither JSON nor msgpack")
