"""v2 wire format: ``[method_id:u16][req_id:u32][len:u32][protobuf]``.

Each RPC is sent over its own QUIC stream.  The response uses the same
framing on the same stream.
"""

from __future__ import annotations

import struct

# Header: method_id (u16) + req_id (u32) + length (u32) = 10 bytes.
HEADER_FMT = "!HII"  # network byte-order: u16 + u32 + u32
HEADER_SIZE = struct.calcsize(HEADER_FMT)

# Method IDs (mirrors quicproquo-proto/src/lib.rs::method_ids).
# Auth (100-103)
OPAQUE_REGISTER_START = 100
OPAQUE_REGISTER_FINISH = 101
OPAQUE_LOGIN_START = 102
OPAQUE_LOGIN_FINISH = 103

# Delivery (200-205)
ENQUEUE = 200
FETCH = 201
FETCH_WAIT = 202
PEEK = 203
ACK = 204
BATCH_ENQUEUE = 205

# Keys (300-304)
UPLOAD_KEY_PACKAGE = 300
FETCH_KEY_PACKAGE = 301
UPLOAD_HYBRID_KEY = 302
FETCH_HYBRID_KEY = 303
FETCH_HYBRID_KEYS = 304

# Channel (400)
CREATE_CHANNEL = 400

# User (500-501)
RESOLVE_USER = 500
RESOLVE_IDENTITY = 501

# Blob (600-601)
UPLOAD_BLOB = 600
DOWNLOAD_BLOB = 601

# Device (700-702)
REGISTER_DEVICE = 700
LIST_DEVICES = 701
REVOKE_DEVICE = 702

# P2P (800-802)
PUBLISH_ENDPOINT = 800
RESOLVE_ENDPOINT = 801
HEALTH = 802

# Delete account (950)
DELETE_ACCOUNT = 950


def encode_frame(method_id: int, req_id: int, payload: bytes) -> bytes:
    """Encode a wire frame: header + protobuf payload."""
    header = struct.pack(HEADER_FMT, method_id, req_id, len(payload))
    return header + payload


def decode_header(data: bytes) -> tuple[int, int, int]:
    """Decode a wire frame header, returning (method_id, req_id, payload_len)."""
    if len(data) < HEADER_SIZE:
        raise ValueError(f"header too short: {len(data)} < {HEADER_SIZE}")
    method_id, req_id, length = struct.unpack(HEADER_FMT, data[:HEADER_SIZE])
    return method_id, req_id, length
