// ============================================================================
// Native Unity tests — band_bridge.cpp (PDL_ENABLE_BAND_BRIDGE)
//
//   pio test -e native -f test_band_bridge
// ============================================================================

#define PDL_ENABLE_BAND_BRIDGE 1

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

// Pull in single-TU source files.
#include "../../src/radio_abstraction.cpp"
#include "../../src/band_bridge.cpp"

// ---- mock radio state ----
static bool     g_mock_tx_called = false;
static uint8_t  g_mock_last_tx[64];
static size_t   g_mock_last_tx_len = 0;
static bool     g_mock_tx_return = true;
static bool     g_mock_ack_injected = false;
static uint8_t  g_mock_ack_frame[16];

static bool mock_init()  { return true; }
static bool mock_tx(const uint8_t* buf, size_t len, uint16_t) {
    g_mock_tx_called = true;
    if (len > sizeof(g_mock_last_tx)) len = sizeof(g_mock_last_tx);
    std::memcpy(g_mock_last_tx, buf, len);
    g_mock_last_tx_len = len;
    return g_mock_tx_return;
}
static bool mock_rx(uint8_t* buf, size_t* len, uint16_t) {
    if (!g_mock_ack_injected) { *len = 0; return false; }
    std::memcpy(buf, g_mock_ack_frame, 16);
    *len = 16;
    g_mock_ack_injected = false;
    return true;
}
static int  mock_rssi() { return -60; }

static void install_mock(BandId band) {
    RadioInterface iface;
    iface.band = band;
    iface.init = mock_init;
    iface.tx   = mock_tx;
    iface.rx   = mock_rx;
    iface.rssi_last = mock_rssi;
    iface.name = "MOCK";
    radio_register_mock(band, iface);
}

// ---- helpers: build a valid 32-byte frame with HMAC ----
static uint8_t g_key[32] = {
    1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
    17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32
};

static void build_signed_frame(uint8_t out32[32], uint16_t seq, uint8_t code) {
    std::memset(out32, 0, 32);
    out32[0] = code;
    out32[1] = (uint8_t)(seq >> 8);
    out32[2] = (uint8_t)seq;
    // vehicle_hash at [3..10], issued_at at [11..14], body at [15..27] — all zero for tests
    uint8_t mac[32];
    hmac_sha256(g_key, 32, out32, 28, mac);
    std::memcpy(&out32[28], mac, 4);
}

void setUp(void) {
    radio_abstraction_init();
    install_mock(BandId::CC1101_433);
    band_bridge_init(g_key, 32);
    g_mock_tx_called = false;
    g_mock_last_tx_len = 0;
    g_mock_tx_return = true;
    g_mock_ack_injected = false;
    std::memset(g_mock_last_tx, 0, sizeof(g_mock_last_tx));
}
void tearDown(void) {}


void test_relay_happy_path_no_ack_wait(void) {
    uint8_t frame[32];
    build_signed_frame(frame, 7, 0x06);

    BridgeResult r = band_bridge_relay(BandId::LORA_915, BandId::CC1101_433,
                                       frame, 32, /*require_ack=*/false);
    TEST_ASSERT_EQUAL((int)BridgeStatus::DELIVERED, (int)r.status);
    TEST_ASSERT_EQUAL_UINT16(7, r.sequence);
    TEST_ASSERT_TRUE(g_mock_tx_called);
    // TX'd frame has leading 0x0A prefix then 32-byte payload = 33 bytes
    TEST_ASSERT_EQUAL_size_t(33, g_mock_last_tx_len);
    TEST_ASSERT_EQUAL_UINT8(0x0A, g_mock_last_tx[0]);
    TEST_ASSERT_EQUAL_UINT8(0x06, g_mock_last_tx[1]);  // STANAG code
}

void test_relay_rejects_bad_hmac(void) {
    uint8_t frame[32];
    build_signed_frame(frame, 1, 0x06);
    frame[15] ^= 0xFF;  // tamper body -> HMAC invalid

    BridgeResult r = band_bridge_relay(BandId::LORA_915, BandId::CC1101_433,
                                       frame, 32, false);
    TEST_ASSERT_EQUAL((int)BridgeStatus::HMAC_INVALID, (int)r.status);
    TEST_ASSERT_FALSE(g_mock_tx_called);
}

void test_relay_no_target_radio(void) {
    uint8_t frame[32];
    build_signed_frame(frame, 1, 0x06);

    BridgeResult r = band_bridge_relay(BandId::LORA_915, BandId::NRF24_2G4,
                                       frame, 32, false);
    TEST_ASSERT_EQUAL((int)BridgeStatus::NO_TARGET_RADIO, (int)r.status);
}

void test_duty_cycle_exceeded(void) {
    uint8_t frame[32];
    build_signed_frame(frame, 1, 0x06);

    // Burn CC1101_433 budget (36000 ms) down manually
    for (int i = 0; i < 1000; i++) radio_duty_record(BandId::CC1101_433, 50);
    BridgeResult r = band_bridge_relay(BandId::LORA_915, BandId::CC1101_433,
                                       frame, 32, false);
    TEST_ASSERT_EQUAL((int)BridgeStatus::DUTY_EXCEEDED, (int)r.status);
}

void test_ack_round_trip_with_injection(void) {
    uint8_t frame[32];
    build_signed_frame(frame, 0xCAFE, 0x06);

    // Stage a matching ACK to be returned on the next mock_rx call.
    std::memset(g_mock_ack_frame, 0, 16);
    g_mock_ack_frame[0] = 0x0B;   // MSG_TYPE_UAV_ACK
    g_mock_ack_frame[1] = 0xCA;
    g_mock_ack_frame[2] = 0xFE;
    g_mock_ack_frame[3] = 0;      // status = delivered
    g_mock_ack_injected = true;

    BridgeResult r = band_bridge_relay(BandId::LORA_915, BandId::CC1101_433,
                                       frame, 32, /*require_ack=*/true);
    TEST_ASSERT_EQUAL((int)BridgeStatus::DELIVERED, (int)r.status);
    TEST_ASSERT_EQUAL_UINT16(0xCAFE, r.sequence);
}

void test_ack_timeout_becomes_no_ack(void) {
    uint8_t frame[32];
    build_signed_frame(frame, 99, 0x51);

    // No ACK injected -> timeout path
    BridgeResult r = band_bridge_relay(BandId::LORA_915, BandId::CC1101_433,
                                       frame, 32, /*require_ack=*/true);
    TEST_ASSERT_EQUAL((int)BridgeStatus::NO_ACK_FROM_DRONE, (int)r.status);
}

void test_mesh_ack_build_crc(void) {
    uint8_t out[16];
    size_t n = band_bridge_build_mesh_ack(0x1234, BridgeStatus::DELIVERED,
                                          out, sizeof(out));
    TEST_ASSERT_EQUAL_size_t(16, n);
    TEST_ASSERT_EQUAL_UINT8(PDL_MRSH_MSG_TYPE_MESH_ACK, out[0]);
    TEST_ASSERT_EQUAL_UINT8(0x12, out[1]);
    TEST_ASSERT_EQUAL_UINT8(0x34, out[2]);
    TEST_ASSERT_EQUAL_UINT8((uint8_t)BridgeStatus::DELIVERED, out[3]);
    // Verify CRC
    uint16_t got = ((uint16_t)out[14] << 8) | (uint16_t)out[15];
    uint16_t want = crc16_ccitt(out, 14);
    TEST_ASSERT_EQUAL_UINT16(want, got);
}

int main(int, char**) {
    UNITY_BEGIN();
    RUN_TEST(test_relay_happy_path_no_ack_wait);
    RUN_TEST(test_relay_rejects_bad_hmac);
    RUN_TEST(test_relay_no_target_radio);
    RUN_TEST(test_duty_cycle_exceeded);
    RUN_TEST(test_ack_round_trip_with_injection);
    RUN_TEST(test_ack_timeout_becomes_no_ack);
    RUN_TEST(test_mesh_ack_build_crc);
    return UNITY_END();
}
