// ============================================================================
// test/test_reticulum_identity/test_reticulum_identity.cpp
//
// Unity tests for src/reticulum_identity.{h,cpp} -- a real, wire-compatible
// Reticulum (RNS) node identity + self-announce packet builder.
//
// The SHA-256 vectors are standard FIPS 180-4 known-answer tests. The
// Identity/Destination/announce vectors were generated with the REAL `RNS`
// Python reference implementation (pip install rns), by constructing an
// RNS.Identity from these exact seeds (bypassing its normal random key
// generation), building an RNS.Destination("pdl","t1sensor"), and calling
// .announce() with a monkeypatched deterministic time.time() and
// RNS.Identity.get_random_hash() so the output is fully reproducible. Every
// intermediate value (name_hash, identity hash, destination hash,
// signed_data, signature, and the final packed announce bytes) was
// cross-checked byte-for-byte against the real library before being baked
// in here -- this is an end-to-end interop check against upstream RNS, not
// self-consistency.
//
// Run with:
//   pio test -e native -f test_reticulum_identity
// ============================================================================

#include <unity.h>
#include <cstring>
#include "../../src/ota_signed.cpp"
#include "../../src/reticulum_identity.cpp"

void setUp(void) {}
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]));
    }
}

// ---------------------------------------------------------------------------
// SHA-256 -- standard FIPS 180-4 known-answer vectors
// ---------------------------------------------------------------------------
void test_sha256_empty_string(void) {
    uint8_t out[32], expected[32];
    hex2bin("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", expected, 32);
    rns_full_hash(nullptr, 0, out);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, out, 32);
}

void test_sha256_abc(void) {
    uint8_t out[32], expected[32];
    hex2bin("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", expected, 32);
    rns_full_hash((const uint8_t*)"abc", 3, out);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, out, 32);
}

// ---------------------------------------------------------------------------
// Identity / Destination hashing -- verified against real RNS
// ---------------------------------------------------------------------------
static void make_test_identity(RnsIdentity* id) {
    uint8_t ed_seed[32], x_seed[32];
    for (int i = 0; i < 32; i++) ed_seed[i] = (uint8_t)i;
    for (int i = 0; i < 32; i++) x_seed[i] = 0x01;
    rns_identity_from_seeds(ed_seed, x_seed, id);
}

void test_identity_hash_matches_real_rns(void) {
    RnsIdentity id;
    make_test_identity(&id);
    uint8_t expected[16];
    hex2bin("8c8ab48cc9a47bdaeeac90947633f9ed", expected, 16);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, id.hash, 16);
}

void test_name_hash_matches_real_rns(void) {
    const char* aspects[] = {"t1sensor"};
    uint8_t name_hash[RNS_NAME_HASHLEN];
    rns_name_hash("pdl", aspects, 1, name_hash);
    uint8_t expected[10];
    hex2bin("78f31f8eecb1cc852611", expected, 10);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, name_hash, 10);
}

void test_destination_hash_matches_real_rns(void) {
    RnsIdentity id;
    make_test_identity(&id);
    const char* aspects[] = {"t1sensor"};
    uint8_t name_hash[RNS_NAME_HASHLEN];
    rns_name_hash("pdl", aspects, 1, name_hash);
    uint8_t dest_hash[RNS_TRUNCATED_HASHLEN];
    rns_destination_hash(name_hash, id.hash, dest_hash);
    uint8_t expected[16];
    hex2bin("63b37ed37402dab09ecb12dd8be74853", expected, 16);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, dest_hash, 16);
}

// ---------------------------------------------------------------------------
// Full announce packet -- end-to-end byte-exact match against real RNS
// ---------------------------------------------------------------------------
void test_build_announce_matches_real_rns_byte_exact(void) {
    RnsIdentity id;
    make_test_identity(&id);

    const char* aspects[] = {"t1sensor"};
    const char* app_data = "t1-spencer01";
    uint8_t random_hash[RNS_RANDOM_HASHLEN];
    hex2bin("aaaaaaaaaa006a18a500", random_hash, 10);

    uint8_t out[RNS_ANNOUNCE_MAX_BYTES];
    size_t n = rns_build_announce(&id, "pdl", aspects, 1,
                                  (const uint8_t*)app_data, strlen(app_data),
                                  random_hash, out, sizeof(out));

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

void test_build_announce_rejects_undersized_buffer(void) {
    RnsIdentity id;
    make_test_identity(&id);
    const char* aspects[] = {"t1sensor"};
    uint8_t random_hash[RNS_RANDOM_HASHLEN] = {0};
    uint8_t tiny[10];
    size_t n = rns_build_announce(&id, "pdl", aspects, 1, nullptr, 0, random_hash, tiny, sizeof(tiny));
    TEST_ASSERT_EQUAL_size_t(0, n);
}

void test_build_announce_no_app_data(void) {
    RnsIdentity id;
    make_test_identity(&id);
    const char* aspects[] = {"t1sensor"};
    uint8_t random_hash[RNS_RANDOM_HASHLEN] = {0};
    uint8_t out[RNS_ANNOUNCE_MAX_BYTES];
    size_t n = rns_build_announce(&id, "pdl", aspects, 1, nullptr, 0, random_hash, out, sizeof(out));
    // header(19) + pubkey(64) + name_hash(10) + random_hash(10) + sig(64), no app_data
    TEST_ASSERT_EQUAL_size_t(19 + 64 + 10 + 10 + 64, n);
}

// ---------------------------------------------------------------------------
// Identity persistence (native stub)
// ---------------------------------------------------------------------------
void test_identity_init_is_idempotent(void) {
    RnsIdentity saved;
    make_test_identity(&saved);
    rns_identity_test_set(&saved);

    const RnsIdentity* got1 = rns_identity_get();
    const RnsIdentity* got2 = rns_identity_get();
    TEST_ASSERT_EQUAL_UINT8_ARRAY(saved.hash, got1->hash, 16);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(got1->hash, got2->hash, 16);
}

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

    RUN_TEST(test_sha256_empty_string);
    RUN_TEST(test_sha256_abc);

    RUN_TEST(test_identity_hash_matches_real_rns);
    RUN_TEST(test_name_hash_matches_real_rns);
    RUN_TEST(test_destination_hash_matches_real_rns);

    RUN_TEST(test_build_announce_matches_real_rns_byte_exact);
    RUN_TEST(test_build_announce_rejects_undersized_buffer);
    RUN_TEST(test_build_announce_no_app_data);

    RUN_TEST(test_identity_init_is_idempotent);

    return UNITY_END();
}
