//! Appendix C wire vectors from `PROTOCOL.md`.
//!
//! Each test pins one or more complete wire-framed byte sequences from
//! the spec. If a future refactor accidentally redefines a field layout
//! or a CRC variant, the test fails with a concrete hex diff against
//! the normative document — not just against the crate's own round-trip.

#![allow(clippy::unwrap_used, clippy::panic, clippy::expect_used)]

use donglora_protocol::{
    Command, DeviceMessage, ErrorCode, FrameDecoder, FrameResult, Info, LoRaBandwidth,
    LoRaCodingRate, LoRaConfig, LoRaHeaderMode, MAX_WIRE_FRAME, Modulation, OkPayload, Owner,
    RadioChipId, RxOrigin, RxPayload, SetConfigResult, SetConfigResultCode, TxDonePayload, TxFlags,
    TxResult, cap, commands, encode_frame,
};
use heapless::Vec as HVec;

// ── Helpers ─────────────────────────────────────────────────────────

/// Encode a command as a complete wire-ready frame, tag-correlated.
fn encode_cmd_frame(cmd: &Command, tag: u16) -> HVec<u8, MAX_WIRE_FRAME> {
    let mut payload_buf = [0u8; 320];
    let payload_len = cmd.encode_payload(&mut payload_buf).unwrap();
    let mut wire = [0u8; MAX_WIRE_FRAME];
    let n = encode_frame(cmd.type_id(), tag, &payload_buf[..payload_len], &mut wire).unwrap();
    let mut out = HVec::new();
    out.extend_from_slice(&wire[..n]).unwrap();
    out
}

/// Encode a device message as a complete wire-ready frame, tag-correlated.
fn encode_dev_frame(msg: &DeviceMessage, tag: u16) -> HVec<u8, MAX_WIRE_FRAME> {
    let mut payload_buf = [0u8; 320];
    let payload_len = msg.encode_payload(&mut payload_buf).unwrap();
    let mut wire = [0u8; MAX_WIRE_FRAME];
    let n = encode_frame(msg.type_id(), tag, &payload_buf[..payload_len], &mut wire).unwrap();
    let mut out = HVec::new();
    out.extend_from_slice(&wire[..n]).unwrap();
    out
}

/// Decode a single frame out of `wire` and assert everything about it.
fn decode_single_frame(wire: &[u8]) -> (u8, u16, HVec<u8, 320>) {
    let mut decoder = FrameDecoder::new();
    let mut out: Option<(u8, u16, HVec<u8, 320>)> = None;
    decoder.feed(wire, |res| match res {
        FrameResult::Ok {
            type_id,
            tag,
            payload,
        } => {
            let mut p = HVec::new();
            p.extend_from_slice(payload).unwrap();
            out = Some((type_id, tag, p));
        }
        FrameResult::Err(e) => panic!("decode error: {:?}", e),
    });
    out.expect("expected one frame")
}

// ── §C.2.1 PING / OK ────────────────────────────────────────────────

#[test]
fn c21_ping_encodes_to_spec_bytes() {
    // H→D  PING  tag=0x0001 → wire: 03 01 01 03 9D C8 00
    let wire = encode_cmd_frame(&Command::Ping, 0x0001);
    assert_eq!(wire.as_slice(), &[0x03, 0x01, 0x01, 0x03, 0x9D, 0xC8, 0x00]);
}

#[test]
fn c21_ok_encodes_to_spec_bytes() {
    // D→H  OK  tag=0x0001 → wire: 03 80 01 03 F7 C4 00
    let wire = encode_dev_frame(&DeviceMessage::Ok(OkPayload::Empty), 0x0001);
    assert_eq!(wire.as_slice(), &[0x03, 0x80, 0x01, 0x03, 0xF7, 0xC4, 0x00]);
}

#[test]
fn c21_decode_roundtrip() {
    let (type_id, tag, payload) = decode_single_frame(&[0x03, 0x01, 0x01, 0x03, 0x9D, 0xC8, 0x00]);
    assert_eq!(type_id, commands::TYPE_PING);
    assert_eq!(tag, 0x0001);
    assert_eq!(payload.as_slice(), &[]);
    assert_eq!(Command::parse(type_id, &payload).unwrap(), Command::Ping);
}

// ── §C.2.3 SET_CONFIG LoRa EU868 ────────────────────────────────────

fn c23_lora() -> LoRaConfig {
    LoRaConfig {
        freq_hz: 868_100_000,
        sf: 7,
        bw: LoRaBandwidth::Khz125,
        cr: LoRaCodingRate::Cr4_5,
        preamble_len: 8,
        sync_word: 0x1424,
        tx_power_dbm: 14,
        header_mode: LoRaHeaderMode::Explicit,
        payload_crc: true,
        iq_invert: false,
    }
}

#[test]
fn c23_set_config_encodes_to_spec_bytes() {
    // H→D  SET_CONFIG  tag=0x0003 → wire:
    //   03 03 03 08 01 A0 27 BE 33 07 07 02 08 04 24 14 0E 02 01 03 D9 1F 00
    let cmd = Command::SetConfig(Modulation::LoRa(c23_lora()));
    let wire = encode_cmd_frame(&cmd, 0x0003);
    let expected: &[u8] = &[
        0x03, 0x03, 0x03, 0x08, 0x01, 0xA0, 0x27, 0xBE, 0x33, 0x07, 0x07, 0x02, 0x08, 0x04, 0x24,
        0x14, 0x0E, 0x02, 0x01, 0x03, 0xD9, 0x1F, 0x00,
    ];
    assert_eq!(wire.as_slice(), expected);
}

#[test]
fn c23_set_config_ok_response() {
    // D→H  OK  tag=0x0003 result=APPLIED owner=MINE current=[same LoRa]
    let result = SetConfigResult {
        result: SetConfigResultCode::Applied,
        owner: Owner::Mine,
        current: Modulation::LoRa(c23_lora()),
    };
    let msg = DeviceMessage::Ok(OkPayload::SetConfig(result));
    let wire = encode_dev_frame(&msg, 0x0003);
    let expected: &[u8] = &[
        0x03, 0x80, 0x03, 0x01, 0x09, 0x01, 0x01, 0xA0, 0x27, 0xBE, 0x33, 0x07, 0x07, 0x02, 0x08,
        0x04, 0x24, 0x14, 0x0E, 0x02, 0x01, 0x03, 0xC8, 0x91, 0x00,
    ];
    assert_eq!(wire.as_slice(), expected);
}

// ── §C.2.4 TX with default CAD, packet "Hello" ──────────────────────

#[test]
fn c24_tx_with_cad() {
    // H→D  TX  tag=0x0004 flags=0x00 data="Hello"
    // wire: 03 04 04 01 08 48 65 6C 6C 6F 26 40 00
    let mut data = HVec::new();
    data.extend_from_slice(b"Hello").unwrap();
    let cmd = Command::Tx {
        flags: TxFlags::default(),
        data,
    };
    let wire = encode_cmd_frame(&cmd, 0x0004);
    let expected: &[u8] = &[
        0x03, 0x04, 0x04, 0x01, 0x08, 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x26, 0x40, 0x00,
    ];
    assert_eq!(wire.as_slice(), expected);
}

#[test]
fn c24_tx_ok() {
    let wire = encode_dev_frame(&DeviceMessage::Ok(OkPayload::Empty), 0x0004);
    assert_eq!(wire.as_slice(), &[0x03, 0x80, 0x04, 0x03, 0x02, 0x3B, 0x00]);
}

#[test]
fn c24_tx_done() {
    let td = TxDonePayload {
        result: TxResult::Transmitted,
        airtime_us: 30_976,
    };
    let wire = encode_dev_frame(&DeviceMessage::TxDone(td), 0x0004);
    let expected: &[u8] = &[
        0x03, 0xC1, 0x04, 0x01, 0x01, 0x02, 0x79, 0x01, 0x03, 0xE3, 0xFA, 0x00,
    ];
    assert_eq!(wire.as_slice(), expected);
}

// ── §C.2.5 TX with skip_cad ─────────────────────────────────────────

#[test]
fn c25_tx_skip_cad() {
    // H→D  TX  tag=0x0005 flags=0x01 data="URGENT"
    // wire: 03 04 05 0A 01 55 52 47 45 4E 54 DB 1C 00
    let mut data = HVec::new();
    data.extend_from_slice(b"URGENT").unwrap();
    let cmd = Command::Tx {
        flags: TxFlags { skip_cad: true },
        data,
    };
    let wire = encode_cmd_frame(&cmd, 0x0005);
    let expected: &[u8] = &[
        0x03, 0x04, 0x05, 0x0A, 0x01, 0x55, 0x52, 0x47, 0x45, 0x4E, 0x54, 0xDB, 0x1C, 0x00,
    ];
    assert_eq!(wire.as_slice(), expected);
}

#[test]
fn c25_tx_done() {
    let td = TxDonePayload {
        result: TxResult::Transmitted,
        airtime_us: 33_792,
    };
    let wire = encode_dev_frame(&DeviceMessage::TxDone(td), 0x0005);
    let expected: &[u8] = &[
        0x03, 0xC1, 0x05, 0x01, 0x01, 0x02, 0x84, 0x01, 0x03, 0x81, 0xE3, 0x00,
    ];
    assert_eq!(wire.as_slice(), expected);
}

// ── §C.2.6 RX event ─────────────────────────────────────────────────

#[test]
fn c26_rx_start() {
    let wire = encode_cmd_frame(&Command::RxStart, 0x0006);
    assert_eq!(wire.as_slice(), &[0x03, 0x05, 0x06, 0x03, 0xCA, 0x8D, 0x00]);
}

#[test]
fn c26_rx_event() {
    let mut data = HVec::new();
    data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]).unwrap();
    let rx = RxPayload {
        rssi_tenths_dbm: -735,
        snr_tenths_db: 95,
        freq_err_hz: -125,
        timestamp_us: 42_000_000,
        crc_valid: true,
        packets_dropped: 0,
        origin: RxOrigin::Ota,
        data,
    };
    let wire = encode_dev_frame(&DeviceMessage::Rx(rx), 0x0000);
    let expected: &[u8] = &[
        0x02, 0xC0, 0x01, 0x04, 0x21, 0xFD, 0x5F, 0x09, 0x83, 0xFF, 0xFF, 0xFF, 0x80, 0xDE, 0x80,
        0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x07, 0x01, 0x02, 0x03, 0x04, 0xB9, 0x8E,
        0x00,
    ];
    assert_eq!(wire.as_slice(), expected);
}

#[test]
fn c26_rx_stop() {
    let wire = encode_cmd_frame(&Command::RxStop, 0x0008);
    assert_eq!(wire.as_slice(), &[0x03, 0x06, 0x08, 0x03, 0x95, 0xF7, 0x00]);
}

// ── §C.5.1 ERR(ENOTCONFIGURED) ──────────────────────────────────────

#[test]
fn c51_err_notconfigured() {
    let wire = encode_dev_frame(&DeviceMessage::Err(ErrorCode::ENotConfigured), 0x0028);
    let expected: &[u8] = &[0x03, 0x81, 0x28, 0x02, 0x03, 0x03, 0x53, 0x7E, 0x00];
    assert_eq!(wire.as_slice(), expected);
}

// ── §C.5.3 ERR(EPARAM) ──────────────────────────────────────────────

#[test]
fn c53_err_eparam() {
    let wire = encode_dev_frame(&DeviceMessage::Err(ErrorCode::EParam), 0x002A);
    let expected: &[u8] = &[0x03, 0x81, 0x2A, 0x02, 0x01, 0x03, 0x59, 0xF5, 0x00];
    assert_eq!(wire.as_slice(), expected);
}

// ── §C.5.4 ERR(EBUSY) ───────────────────────────────────────────────

#[test]
fn c54_err_ebusy() {
    let wire = encode_dev_frame(&DeviceMessage::Err(ErrorCode::EBusy), 0x002B);
    let expected: &[u8] = &[0x03, 0x81, 0x2B, 0x02, 0x06, 0x03, 0x7A, 0x1A, 0x00];
    assert_eq!(wire.as_slice(), expected);
}

// ── §C.5.6 Unknown command type byte ────────────────────────────────

#[test]
fn c56_unknown_command_type_encodes_to_spec() {
    // The host sends type=0x10 with payload DE AD. We can't use
    // Command::encode_payload (no such variant), but the frame encoder
    // is type-agnostic so we can directly produce the wire bytes.
    let mut wire = [0u8; MAX_WIRE_FRAME];
    let n = encode_frame(0x10, 0x003C, &[0xDE, 0xAD], &mut wire).unwrap();
    let expected: &[u8] = &[0x03, 0x10, 0x3C, 0x05, 0xDE, 0xAD, 0xE2, 0x24, 0x00];
    assert_eq!(&wire[..n], expected);
}

#[test]
fn c56_unknown_command_decoder_still_parses_frame() {
    // The decoder is payload-agnostic: it yields (type_id=0x10, tag=0x003C,
    // payload=DE AD). The command parser then rejects it cleanly with
    // UnknownType so the caller can respond with ERR(EUNKNOWN_CMD).
    let wire: &[u8] = &[0x03, 0x10, 0x3C, 0x05, 0xDE, 0xAD, 0xE2, 0x24, 0x00];
    let (type_id, tag, payload) = decode_single_frame(wire);
    assert_eq!(type_id, 0x10);
    assert_eq!(tag, 0x003C);
    assert_eq!(payload.as_slice(), &[0xDE, 0xAD]);
    assert!(matches!(
        Command::parse(type_id, &payload),
        Err(donglora_protocol::CommandParseError::UnknownType)
    ));
}

#[test]
fn c56_err_eunknown_cmd_reply() {
    // D→H  ERR  tag=0x003C  code=0x0005
    //   on wire: 03 81 3C 02 05 03 A3 05 00
    let wire = encode_dev_frame(&DeviceMessage::Err(ErrorCode::EUnknownCmd), 0x003C);
    let expected: &[u8] = &[0x03, 0x81, 0x3C, 0x02, 0x05, 0x03, 0xA3, 0x05, 0x00];
    assert_eq!(wire.as_slice(), expected);
}

// ── §C.6.2 RX with crc_valid = 0 ────────────────────────────────────

#[test]
fn c62_rx_bad_crc_delivered_anyway() {
    let mut data = HVec::new();
    data.extend_from_slice(&[0x41, 0x42, 0xFF, 0x00, 0xCC])
        .unwrap();
    let rx = RxPayload {
        rssi_tenths_dbm: -1050,
        snr_tenths_db: -80,
        freq_err_hz: 2200,
        timestamp_us: 120_000_000,
        crc_valid: false,
        packets_dropped: 0,
        origin: RxOrigin::Ota,
        data,
    };
    let wire = encode_dev_frame(&DeviceMessage::Rx(rx), 0x0000);
    let expected: &[u8] = &[
        0x02, 0xC0, 0x01, 0x07, 0xE6, 0xFB, 0xB0, 0xFF, 0x98, 0x08, 0x01, 0x01, 0x04, 0x0E, 0x27,
        0x07, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x41, 0x42, 0xFF, 0x04, 0xCC, 0x04,
        0x3C, 0x00,
    ];
    assert_eq!(wire.as_slice(), expected);
}

// ── §C.6.4 Async ERR(EFRAME) ────────────────────────────────────────

#[test]
fn c64_async_err_eframe() {
    let wire = encode_dev_frame(&DeviceMessage::Err(ErrorCode::EFrame), 0x0000);
    let expected: &[u8] = &[0x02, 0x81, 0x01, 0x05, 0x02, 0x01, 0xCE, 0xEF, 0x00];
    assert_eq!(wire.as_slice(), expected);
}

// ── §C.6.5 Async ERR(ERADIO) ────────────────────────────────────────

#[test]
fn c65_async_err_eradio() {
    let wire = encode_dev_frame(&DeviceMessage::Err(ErrorCode::ERadio), 0x0000);
    let expected: &[u8] = &[0x02, 0x81, 0x01, 0x05, 0x01, 0x01, 0x9D, 0xBA, 0x00];
    assert_eq!(wire.as_slice(), expected);
}

// ── §C.8.1 COBS edge: no zeros in body ──────────────────────────────

#[test]
fn c81_ping_no_zeros_in_body() {
    // H→D  PING  tag=0x0101 → no zero bytes in tag bytes 01 01.
    // wire: 06 01 01 01 BC D8 00
    let wire = encode_cmd_frame(&Command::Ping, 0x0101);
    assert_eq!(wire.as_slice(), &[0x06, 0x01, 0x01, 0x01, 0xBC, 0xD8, 0x00]);
}

// ── §C.2.2 GET_INFO ─────────────────────────────────────────────────

#[test]
fn c22_get_info_request() {
    let wire = encode_cmd_frame(&Command::GetInfo, 0x0002);
    assert_eq!(wire.as_slice(), &[0x03, 0x02, 0x02, 0x03, 0x9E, 0xC4, 0x00]);
}

#[test]
fn c22_get_info_response() {
    // Full SX1262 GET_INFO response encoded as a complete wire frame.
    let mut mcu = [0u8; donglora_protocol::MAX_MCU_UID_LEN];
    mcu[..8].copy_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x23, 0x45, 0x67]);
    let info = Info {
        proto_major: 1,
        proto_minor: 0,
        fw_major: 0,
        fw_minor: 1,
        fw_patch: 0,
        radio_chip_id: RadioChipId::Sx1262.as_u16(),
        capability_bitmap: cap::LORA | cap::FSK | cap::CAD_BEFORE_TX,
        supported_sf_bitmap: 0x1FE0,
        supported_bw_bitmap: 0x03FF,
        max_payload_bytes: 255,
        rx_queue_capacity: 64,
        tx_queue_capacity: 16,
        freq_min_hz: 150_000_000,
        freq_max_hz: 960_000_000,
        tx_power_min_dbm: -9,
        tx_power_max_dbm: 22,
        mcu_uid_len: 8,
        mcu_uid: mcu,
        radio_uid_len: 0,
        radio_uid: [0u8; donglora_protocol::MAX_RADIO_UID_LEN],
    };
    let wire = encode_dev_frame(&DeviceMessage::Ok(OkPayload::Info(info)), 0x0002);
    let expected: &[u8] = &[
        0x03, 0x80, 0x02, 0x02, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03, 0x02, 0x01, 0x01,
        0x01, 0x01, 0x01, 0x06, 0xE0, 0x1F, 0xFF, 0x03, 0xFF, 0x02, 0x40, 0x02, 0x10, 0x05, 0x80,
        0xD1, 0xF0, 0x08, 0x0F, 0x70, 0x38, 0x39, 0xF7, 0x16, 0x08, 0xDE, 0xAD, 0xBE, 0xEF, 0x01,
        0x23, 0x45, 0x67, 0x03, 0xFA, 0xA4, 0x00,
    ];
    assert_eq!(wire.as_slice(), expected);
}

// ── Decode-side roundtrip for the key command shapes ───────────────

#[test]
fn decode_tx_roundtrip() {
    let mut data = HVec::new();
    data.extend_from_slice(b"Hello").unwrap();
    let cmd = Command::Tx {
        flags: TxFlags::default(),
        data,
    };
    let wire = encode_cmd_frame(&cmd, 0x0004);
    let (type_id, tag, payload) = decode_single_frame(&wire);
    assert_eq!(type_id, commands::TYPE_TX);
    assert_eq!(tag, 0x0004);
    assert_eq!(Command::parse(type_id, &payload).unwrap(), cmd);
}

#[test]
fn decode_set_config_roundtrip() {
    let cmd = Command::SetConfig(Modulation::LoRa(c23_lora()));
    let wire = encode_cmd_frame(&cmd, 0x0003);
    let (type_id, tag, payload) = decode_single_frame(&wire);
    assert_eq!(type_id, commands::TYPE_SET_CONFIG);
    assert_eq!(tag, 0x0003);
    assert_eq!(Command::parse(type_id, &payload).unwrap(), cmd);
}

#[test]
fn decode_rx_roundtrip() {
    let mut data = HVec::new();
    data.extend_from_slice(&[0x01, 0x02, 0x03, 0x04]).unwrap();
    let rx = RxPayload {
        rssi_tenths_dbm: -735,
        snr_tenths_db: 95,
        freq_err_hz: -125,
        timestamp_us: 42_000_000,
        crc_valid: true,
        packets_dropped: 0,
        origin: RxOrigin::Ota,
        data: data.clone(),
    };
    let wire = encode_dev_frame(&DeviceMessage::Rx(rx.clone()), 0x0000);
    let (type_id, tag, payload) = decode_single_frame(&wire);
    assert_eq!(type_id, 0xC0);
    assert_eq!(tag, 0x0000);
    let decoded = DeviceMessage::parse(type_id, &payload, None).unwrap();
    assert_eq!(decoded, DeviceMessage::Rx(rx));
}
