// ============================================================================
// test/test_rnode_bridge/test_rnode_bridge.cpp
//
// Unity tests for src/rnode_bridge.{h,cpp}. This file had ZERO native test
// coverage before 2026-07-30 -- and turned out to have a real, previously
// undetected bug: RNODE_L1_HEADER_LEN was 18 bytes, but real Reticulum's
// actual wire header (confirmed by reading RNS.Packet.pack() in the real
// `RNS` Python reference implementation) is 19 bytes with a different field
// order (flags, hops, dest_hash, context -- not header_type, context,
// dest_hash). The old code read every inbound field one position off from
// where a real RNS peer would have put it. Fixed alongside adding a real
// self-announce (see reticulum_identity.h) to replace the old design where
// an operator had to supply an arbitrary destination hash to "announce" --
// which was never valid RNS usage (an announce proves you hold the private
// key for the destination; you can only ever announce your own identity).
//
// Run with:
//   pio test -e native -f test_rnode_bridge
// ============================================================================

#include <unity.h>
#include <cstring>

#include "../../src/ota_signed.cpp"
#include "../../src/reticulum_identity.cpp"
#include "../../src/radio_scheduler.cpp"
#include "../../src/meshtastic_emit.cpp"   // provides radio_mesh_set_params() -- the UNIT_TEST-guarded weak fallback in radio_scheduler.cpp is skipped, same convention test_interop.cpp already uses
#include "../../src/rnode_bridge.cpp"

void setUp(void) {
    RnsIdentity id;
    uint8_t ed_seed[32], x_seed[32];
    for (int i = 0; i < 32; i++) { ed_seed[i] = (uint8_t)i; x_seed[i] = 0x01; }
    rns_identity_from_seeds(ed_seed, x_seed, &id);
    rns_identity_test_set(&id);
    rnode_init();   // native branch sets s_rn_enabled = true unconditionally
}
void tearDown(void) {}

static void hex2bin(const char* hex, uint8_t* out, size_t out_len) {
    for (size_t i = 0; i < out_len; i++) {
        auto nib = [](char c) -> uint8_t {
            if (c >= '0' && c <= '9') return (uint8_t)(c - '0');
            return (uint8_t)(c - 'a' + 10);
        };
        out[i] = (uint8_t)((nib(hex[2*i]) << 4) | nib(hex[2*i + 1]));
    }
}

// ---------------------------------------------------------------------------
// KISS encode/decode round trip
// ---------------------------------------------------------------------------
void test_kiss_round_trip_no_special_bytes(void) {
    uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05};
    uint8_t framed[32], decoded[32];
    size_t flen = rnode_test_kiss_encode(data, sizeof(data), framed, sizeof(framed));
    TEST_ASSERT_TRUE(flen > 0);
    size_t dlen = rnode_test_kiss_decode(framed, flen, decoded, sizeof(decoded));
    TEST_ASSERT_EQUAL_size_t(sizeof(data), dlen);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(data, decoded, sizeof(data));
}

void test_kiss_round_trip_escapes_fend_and_fesc(void) {
    uint8_t data[] = {0xC0, 0xDB, 0x00, 0xC0, 0xDB};
    uint8_t framed[32], decoded[32];
    size_t flen = rnode_test_kiss_encode(data, sizeof(data), framed, sizeof(framed));
    TEST_ASSERT_TRUE(flen > sizeof(data));  // escaping must grow the frame
    size_t dlen = rnode_test_kiss_decode(framed, flen, decoded, sizeof(decoded));
    TEST_ASSERT_EQUAL_size_t(sizeof(data), dlen);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(data, decoded, sizeof(data));
}

// ---------------------------------------------------------------------------
// build_l1 -- exact byte layout, matching real RNS (flags, hops, dest_hash,
// context, payload)
// ---------------------------------------------------------------------------
void test_build_l1_field_layout(void) {
    uint8_t dest_hash[16];
    for (int i = 0; i < 16; i++) dest_hash[i] = (uint8_t)(0x10 + i);
    uint8_t payload[] = {0xAA, 0xBB, 0xCC};

    uint8_t out[64];
    size_t n = rnode_test_build_l1(0x01, 0x00, dest_hash, 0x00, payload, sizeof(payload), out, sizeof(out));

    TEST_ASSERT_EQUAL_size_t(19 + 3, n);
    TEST_ASSERT_EQUAL_UINT8(0x01, out[0]);   // flags
    TEST_ASSERT_EQUAL_UINT8(0x00, out[1]);   // hops
    TEST_ASSERT_EQUAL_UINT8_ARRAY(dest_hash, out + 2, 16);
    TEST_ASSERT_EQUAL_UINT8(0x00, out[18]);  // context
    TEST_ASSERT_EQUAL_UINT8_ARRAY(payload, out + 19, 3);
}

void test_build_l1_rejects_undersized_buffer(void) {
    uint8_t dest_hash[16] = {0};
    uint8_t tiny[5];
    size_t n = rnode_test_build_l1(0, 0, dest_hash, 0, nullptr, 0, tiny, sizeof(tiny));
    TEST_ASSERT_EQUAL_size_t(0, n);
}

// ---------------------------------------------------------------------------
// rnode_build_announce_frame -- end-to-end byte-exact match against a real
// RNS announce (same identity/app_name/aspects/random_hash/app_data vector
// verified in test_reticulum_identity.cpp), decoded back out of its KISS
// frame to sidestep hand-computing the escaped wire bytes.
// ---------------------------------------------------------------------------
void test_build_announce_frame_matches_real_rns_after_kiss_decode(void) {
    const char* aspects[] = {"t1sensor"};
    const char* app_data = "t1-spencer01";
    uint8_t random_hash[10];
    hex2bin("aaaaaaaaaa006a18a500", random_hash, 10);

    uint8_t kiss_out[512];
    size_t kiss_len = rnode_build_announce_frame("pdl", aspects, 1,
                                                 (const uint8_t*)app_data, strlen(app_data),
                                                 random_hash, kiss_out, sizeof(kiss_out));
    TEST_ASSERT_TRUE(kiss_len > 0);

    uint8_t decoded[256];
    size_t decoded_len = rnode_test_kiss_decode(kiss_out, kiss_len, decoded, sizeof(decoded));

    uint8_t expected[179];
    hex2bin("010063b37ed37402dab09ecb12dd8be7485300a4e09292b651c278b9772c569f5fa9bb13d906b46ab68c9df9dc2b4409f8a20903a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b878f31f8eecb1cc852611aaaaaaaaaa006a18a5003d3ef2c7421c4ff671fa664ee1c015baef77daee4bf69b1d20a1709b4e670fe67767cad531f90949308be08333a46c4ad929ed70d350d9c10e60f3a96bfe800b74312d7370656e6365723031",
            expected, 179);
    TEST_ASSERT_EQUAL_size_t(179, decoded_len);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, decoded, 179);
}

// ---------------------------------------------------------------------------
// rnode_handle_packet -- inbound field extraction at the FIXED offsets
// ---------------------------------------------------------------------------
void test_handle_packet_extracts_correct_offsets_after_fix(void) {
    // Build a raw (non-KISS-framed) L1 packet by hand with a distinctive
    // context byte and payload, and confirm the function doesn't reject it
    // (rx_packets increments) -- the field values themselves are only
    // observable via the ARDUINO-only MSG_TYPE_INTEROP_RX wrapper, so this
    // native test's job is to confirm the length/acceptance logic uses the
    // corrected 19-byte header consistently (a `len < RNODE_L1_HEADER_LEN`
    // that used the old 18-byte constant would accept an 18-byte packet
    // that is actually truncated one byte short of a real header).
    uint8_t dest_hash[16] = {0};
    uint8_t payload[] = {0x01, 0x02, 0x03, 0x04};
    uint8_t raw[64];
    size_t n = rnode_test_build_l1(0x01, 0x00, dest_hash, 0x42, payload, sizeof(payload), raw, sizeof(raw));

    RnodeStats before; rnode_get_stats(&before);
    int rc = rnode_handle_packet(raw, n);
    RnodeStats after; rnode_get_stats(&after);

    TEST_ASSERT_EQUAL_INT((int)n, rc);
    TEST_ASSERT_EQUAL_UINT32(before.rx_packets + 1, after.rx_packets);
}

void test_handle_packet_rejects_18_byte_truncated_header(void) {
    // A packet exactly 18 bytes long is one byte short of a real 19-byte
    // header -- must be rejected as a decode failure, not silently parsed
    // as if it had a valid (if empty) payload (that was the old bug's
    // exact blind spot: RNODE_L1_HEADER_LEN==18 made an 18-byte packet
    // look like a complete, zero-payload header).
    uint8_t raw[18] = {0x01, 0x00, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
    RnodeStats before; rnode_get_stats(&before);
    rnode_handle_packet(raw, sizeof(raw));
    RnodeStats after; rnode_get_stats(&after);
    TEST_ASSERT_EQUAL_UINT32(before.rx_decode_failures + 1, after.rx_decode_failures);
}

int main(int, char**) {
    UNITY_BEGIN();

    RUN_TEST(test_kiss_round_trip_no_special_bytes);
    RUN_TEST(test_kiss_round_trip_escapes_fend_and_fesc);

    RUN_TEST(test_build_l1_field_layout);
    RUN_TEST(test_build_l1_rejects_undersized_buffer);

    RUN_TEST(test_build_announce_frame_matches_real_rns_after_kiss_decode);

    RUN_TEST(test_handle_packet_extracts_correct_offsets_after_fix);
    RUN_TEST(test_handle_packet_rejects_18_byte_truncated_header);

    return UNITY_END();
}
