"""Codec 8 / 8 Extended **encoders** for tests + dev tooling.

The production codec module (:mod:`plasmamapper.providers.telematics.teltonika.codec`)
is decode-only by design — encoding AVL packets is what devices do; the
server never originates one in production. These encoders exist so:

  * Round-trip property tests can encode synthetic records, decode the
    result, and verify equality.
  * The ``scripts/codec8-emulator.py`` CLI can drive a live
    ``plasma-ingest`` listener with realistic packet shapes for smoke
    + load testing.
  * Performance benchmarks can fabricate packets without speaking to a
    real device.

Underscore-prefixed module name signals the AP-14 contract: not part
of the public consumer-facing API; dev / test tooling only.
"""

from __future__ import annotations

import struct

from mgf.tele.teltonika._crc import crc16_ibm
from mgf.tele.teltonika.codec import (
    CODEC_8,
    CODEC_8_EXTENDED,
)

__all__ = [
    "build_codec8_record",
    "build_codec8e_record",
    "handshake_bytes",
    "wrap_codec8_envelope",
    "wrap_codec8e_envelope",
]


def handshake_bytes(imei: str) -> bytes:
    """The bytes a device sends as its TCP IMEI handshake (per §2)."""
    encoded = imei.encode("ascii")
    return len(encoded).to_bytes(2, "big") + encoded


def build_codec8_record(
    *,
    ts_ms: int,
    priority: int = 0,
    lon_raw: int = 0,
    lat_raw: int = 0,
    alt: int = 0,
    angle: int = 0,
    satellites: int = 0,
    speed: int = 0,
    event_io_id: int = 0,
    inputs_1b: dict[int, int] | None = None,
    inputs_2b: dict[int, int] | None = None,
    inputs_4b: dict[int, int] | None = None,
    inputs_8b: dict[int, int] | None = None,
) -> bytes:
    """Pack one Codec-8 record (1-byte IO IDs + counts).

    Coords / counts are wire-format (lon/lat as int32 multiplied by 1e7,
    etc.). For a higher-level ergonomics layer use the emulator's helpers.
    """
    inputs_1b = inputs_1b or {}
    inputs_2b = inputs_2b or {}
    inputs_4b = inputs_4b or {}
    inputs_8b = inputs_8b or {}

    out = bytearray()
    out += ts_ms.to_bytes(8, "big")
    out += bytes([priority])
    out += struct.pack(">iihHBH", lon_raw, lat_raw, alt, angle, satellites, speed)

    n_total = len(inputs_1b) + len(inputs_2b) + len(inputs_4b) + len(inputs_8b)
    out += bytes([event_io_id, n_total])
    for size, table in [(1, inputs_1b), (2, inputs_2b), (4, inputs_4b), (8, inputs_8b)]:
        out += bytes([len(table)])
        for io_id, value in table.items():
            out += bytes([io_id])
            out += value.to_bytes(size, "big", signed=False)
    return bytes(out)


def wrap_codec8_envelope(records: list[bytes]) -> bytes:
    """Wrap N Codec-8 records in the universal TCP envelope."""
    return _wrap_envelope(CODEC_8, records)


def build_codec8e_record(
    *,
    ts_ms: int,
    priority: int = 0,
    lon_raw: int = 0,
    lat_raw: int = 0,
    alt: int = 0,
    angle: int = 0,
    satellites: int = 0,
    speed: int = 0,
    event_io_id: int = 0,
    inputs_1b: dict[int, int] | None = None,
    inputs_2b: dict[int, int] | None = None,
    inputs_4b: dict[int, int] | None = None,
    inputs_8b: dict[int, int] | None = None,
    nx_entries: list[tuple[int, bytes]] | None = None,
) -> bytes:
    """Pack one Codec-8 Extended record (2-byte IO IDs + counts + NX)."""
    inputs_1b = inputs_1b or {}
    inputs_2b = inputs_2b or {}
    inputs_4b = inputs_4b or {}
    inputs_8b = inputs_8b or {}
    nx_entries = nx_entries or []

    out = bytearray()
    out += ts_ms.to_bytes(8, "big")
    out += bytes([priority])
    out += struct.pack(">iihHBH", lon_raw, lat_raw, alt, angle, satellites, speed)

    n_total = len(inputs_1b) + len(inputs_2b) + len(inputs_4b) + len(inputs_8b)
    out += event_io_id.to_bytes(2, "big")
    out += n_total.to_bytes(2, "big")
    for size, table in [(1, inputs_1b), (2, inputs_2b), (4, inputs_4b), (8, inputs_8b)]:
        out += len(table).to_bytes(2, "big")
        for io_id, value in table.items():
            out += io_id.to_bytes(2, "big")
            out += value.to_bytes(size, "big", signed=False)
    out += len(nx_entries).to_bytes(2, "big")
    for nx_id, nx_payload in nx_entries:
        out += nx_id.to_bytes(2, "big")
        out += len(nx_payload).to_bytes(2, "big")
        out += nx_payload
    return bytes(out)


def wrap_codec8e_envelope(records: list[bytes]) -> bytes:
    """Wrap N Codec-8 Extended records in the universal TCP envelope."""
    return _wrap_envelope(CODEC_8_EXTENDED, records)


def _wrap_envelope(codec_id: int, records: list[bytes]) -> bytes:
    """Internal: wrap any codec's inner block in the TCP envelope."""
    n = len(records)
    inner = bytes([n]) + b"".join(records) + bytes([n])
    payload = bytes([codec_id]) + inner
    crc = crc16_ibm(payload)
    return b"\x00\x00\x00\x00" + len(payload).to_bytes(4, "big") + payload + crc.to_bytes(4, "big")
