// ============================================================================
// test/test_uav_pairing/test_uav_pairing.cpp
//
// Unity tests for src/uav_pairing.{h,cpp} — PAIR_REQ/PAIR_RSP frame
// handling that fixes the real gap where uav_relay_init()'s HMAC allowlist
// stayed permanently empty (nothing ever called it with a real key). See
// uav_pairing.h for the full gap description and mirrors of
// drone-companion/src/pairing.{h,cpp} (same wire protocol, two codebases).
//
// Uses uav_pairing_test_reset() between tests since the module keeps
// static in-RAM state (mirrors real NVS write-through) -- same pattern as
// ble_companion_test_reset()/mesh_routing's own UNIT_TEST reset hooks
// elsewhere in this repo.
//
// Run with:
//   pio test -e native -f test_uav_pairing
// ============================================================================

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

// uav_pairing.cpp includes uav_relay.h for the msg_type constants only --
// that header doesn't pull in anything native-hostile (no RadioLib etc.),
// so it's safe to include the real one directly here.
#include "../../src/uav_pairing.cpp"

void setUp(void) { uav_pairing_test_reset(); }
void tearDown(void) {}

// ---------------------------------------------------------------------------
// uav_pairing_parse_request
// ---------------------------------------------------------------------------

static void build_pair_req(uint8_t* buf, const uint8_t* root32,
                           const char* drone_id, const char* callsign,
                           bool allow_terminate, UavDroneProfile profile) {
    std::memset(buf, 0, UAV_PAIR_REQ_FRAME_SIZE);
    buf[0] = MSG_TYPE_UAV_PAIR_REQ;
    std::memcpy(buf + 1, root32, 32);
    for (int i = 0; i < 8; i++) buf[33 + i] = 0x40 + i;
    strncpy((char*)buf + 41, drone_id, 23);
    strncpy((char*)buf + 65, callsign, 23);
    for (int i = 0; i < 32; i++) buf[89 + i] = 0x50 + (i % 16);
    buf[121] = allow_terminate ? 1 : 0;
    buf[122] = (uint8_t)profile;
}

void test_parse_valid_pair_request(void) {
    uint8_t root[32];
    for (int i = 0; i < 32; i++) root[i] = (uint8_t)(31 - i);
    uint8_t buf[UAV_PAIR_REQ_FRAME_SIZE];
    build_pair_req(buf, root, "sensor-drone-1", "OP7", true, UavDroneProfile::PX4);

    UavPairingData pd{};
    TEST_ASSERT_TRUE(uav_pairing_parse_request(buf, sizeof(buf), pd));
    TEST_ASSERT_EQUAL_UINT8_ARRAY(root, pd.hmac_root, 32);
    TEST_ASSERT_EQUAL_STRING("sensor-drone-1", pd.drone_id);
    TEST_ASSERT_EQUAL_STRING("OP7", pd.operator_callsign);
    TEST_ASSERT_TRUE(pd.allow_terminate);
    TEST_ASSERT_EQUAL((int)UavDroneProfile::PX4, (int)pd.profile);
}

void test_parse_rejects_wrong_msg_type(void) {
    uint8_t root[32] = {0};
    uint8_t buf[UAV_PAIR_REQ_FRAME_SIZE];
    build_pair_req(buf, root, "d", "c", false, UavDroneProfile::ARDUPILOT_COPTER);
    buf[0] = MSG_TYPE_UAV_CMD;
    UavPairingData pd{};
    TEST_ASSERT_FALSE(uav_pairing_parse_request(buf, sizeof(buf), pd));
}

void test_parse_rejects_wrong_length(void) {
    uint8_t buf[100] = {0};
    buf[0] = MSG_TYPE_UAV_PAIR_REQ;
    UavPairingData pd{};
    TEST_ASSERT_FALSE(uav_pairing_parse_request(buf, sizeof(buf), pd));
}

void test_parse_null_safe(void) {
    UavPairingData pd{};
    TEST_ASSERT_FALSE(uav_pairing_parse_request(nullptr, UAV_PAIR_REQ_FRAME_SIZE, pd));
}

// ---------------------------------------------------------------------------
// uav_pairing_build_response
// ---------------------------------------------------------------------------

void test_build_response_layout_and_crc(void) {
    UavPairResponseFrame r{};
    for (int i = 0; i < 8; i++) r.vehicle_hash[i] = 0x60 + i;
    r.fw_major = 4; r.fw_minor = 5; r.fw_patch = 6;
    r.status = UavPairRspStatus::OK;

    uint8_t out[UAV_PAIR_RSP_FRAME_SIZE];
    size_t n = uav_pairing_build_response(r, out, sizeof(out));
    TEST_ASSERT_EQUAL_size_t(UAV_PAIR_RSP_FRAME_SIZE, n);
    TEST_ASSERT_EQUAL_UINT8(MSG_TYPE_UAV_PAIR_RSP, out[0]);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(r.vehicle_hash, out + 1, 8);
    TEST_ASSERT_EQUAL_UINT8(4, out[9]);
    TEST_ASSERT_EQUAL_UINT8(5, out[10]);
    TEST_ASSERT_EQUAL_UINT8(6, out[11]);
    TEST_ASSERT_EQUAL_UINT8(0, out[12]);

    // Cross-check against a fresh, independently-computed CRC (same
    // XMODEM parameters this repo's own crc16_xmodem() in uav_relay.cpp
    // uses -- see that function's comment for the drone-companion parity
    // note). Verifies internal consistency, not drone-companion's exact
    // implementation (that's ack_crc16(), a separate codebase/test suite).
    uint16_t crc = 0;
    for (int i = 0; i < 14; i++) {
        crc ^= (uint16_t)out[i] << 8;
        for (int b = 0; b < 8; b++) {
            crc = (crc & 0x8000) ? (uint16_t)((crc << 1) ^ 0x1021) : (uint16_t)(crc << 1);
        }
    }
    TEST_ASSERT_EQUAL_UINT8((uint8_t)(crc >> 8), out[14]);
    TEST_ASSERT_EQUAL_UINT8((uint8_t)(crc & 0xFF), out[15]);
}

void test_build_response_buffer_too_small(void) {
    UavPairResponseFrame r{};
    uint8_t tiny[8];
    TEST_ASSERT_EQUAL_size_t(0, uav_pairing_build_response(r, tiny, sizeof(tiny)));
}

// ---------------------------------------------------------------------------
// uav_pairing_process_request
// ---------------------------------------------------------------------------

void test_process_request_rejected_when_window_closed(void) {
    uint8_t root[32] = {0};
    uint8_t buf[UAV_PAIR_REQ_FRAME_SIZE];
    build_pair_req(buf, root, "d", "c", false, UavDroneProfile::ARDUPILOT_COPTER);
    uint8_t vh[8] = {0};
    uint8_t resp[UAV_PAIR_RSP_FRAME_SIZE];
    size_t resp_len = 0;

    TEST_ASSERT_FALSE(uav_pairing_window_open());
    bool sent = uav_pairing_process_request(buf, sizeof(buf), 1000, vh, resp, &resp_len);
    TEST_ASSERT_TRUE(sent);
    TEST_ASSERT_EQUAL_size_t(UAV_PAIR_RSP_FRAME_SIZE, resp_len);
    TEST_ASSERT_EQUAL_UINT8((uint8_t)UavPairRspStatus::PAIR_MODE_REQUIRED, resp[12]);
    TEST_ASSERT_FALSE(uav_pairing_is_paired());
}

void test_process_request_completes_pairing_when_window_open(void) {
    uav_pairing_init(1000);   // cold boot, unpaired -> opens window
    TEST_ASSERT_TRUE(uav_pairing_window_open());

    uint8_t root[32];
    for (int i = 0; i < 32; i++) root[i] = (uint8_t)(i * 7);
    uint8_t buf[UAV_PAIR_REQ_FRAME_SIZE];
    build_pair_req(buf, root, "t1-drone", "OPX", true, UavDroneProfile::BETAFLIGHT);
    uint8_t vh[8] = {0x99, 0x88, 0, 0, 0, 0, 0, 0};
    uint8_t resp[UAV_PAIR_RSP_FRAME_SIZE];
    size_t resp_len = 0;

    bool sent = uav_pairing_process_request(buf, sizeof(buf), 1500, vh, resp, &resp_len);
    TEST_ASSERT_TRUE(sent);
    TEST_ASSERT_EQUAL_UINT8((uint8_t)UavPairRspStatus::OK, resp[12]);
    TEST_ASSERT_EQUAL_UINT8_ARRAY(vh, resp + 1, 8);

    TEST_ASSERT_TRUE(uav_pairing_is_paired());
    uint8_t stored[32];
    TEST_ASSERT_TRUE(uav_pairing_get_hmac_root(stored));
    TEST_ASSERT_EQUAL_UINT8_ARRAY(root, stored, 32);
    TEST_ASSERT_FALSE(uav_pairing_window_open());   // pairing closes the window
}

void test_process_request_malformed_frame_sends_nothing(void) {
    uav_pairing_init(1000);
    uint8_t buf[UAV_PAIR_REQ_FRAME_SIZE] = {0};
    buf[0] = MSG_TYPE_UAV_CMD;   // wrong msg_type
    uint8_t vh[8] = {0};
    uint8_t resp[UAV_PAIR_RSP_FRAME_SIZE];
    size_t resp_len = 999;

    bool sent = uav_pairing_process_request(buf, sizeof(buf), 1500, vh, resp, &resp_len);
    TEST_ASSERT_FALSE(sent);
    TEST_ASSERT_EQUAL_size_t(0, resp_len);
    TEST_ASSERT_FALSE(uav_pairing_is_paired());
}

// ---------------------------------------------------------------------------
// uav_pairing_init / uav_pairing_tick
// ---------------------------------------------------------------------------

void test_init_opens_window_on_cold_boot(void) {
    // Native tests never have a prior-boot NVS entry (load_persisted_
    // pairing() always returns false under UNIT_TEST), so init() should
    // always behave like a genuine cold boot here.
    uav_pairing_init(5000);
    TEST_ASSERT_TRUE(uav_pairing_window_open());
    TEST_ASSERT_FALSE(uav_pairing_is_paired());
}

void test_tick_button_hold_opens_window(void) {
    TEST_ASSERT_FALSE(uav_pairing_window_open());
    uav_pairing_tick(1000, /*button_held_ms=*/UAV_PAIR_BTN_HOLD_MS);
    TEST_ASSERT_TRUE(uav_pairing_window_open());
}

void test_tick_short_button_press_does_not_open_window(void) {
    uav_pairing_tick(1000, /*button_held_ms=*/1000);
    TEST_ASSERT_FALSE(uav_pairing_window_open());
}

void test_tick_closes_window_after_deadline(void) {
    uav_pairing_init(1000);
    TEST_ASSERT_TRUE(uav_pairing_window_open());
    uav_pairing_tick(1000 + UAV_PAIR_WINDOW_MS, 0);
    TEST_ASSERT_FALSE(uav_pairing_window_open());
}

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

    RUN_TEST(test_parse_valid_pair_request);
    RUN_TEST(test_parse_rejects_wrong_msg_type);
    RUN_TEST(test_parse_rejects_wrong_length);
    RUN_TEST(test_parse_null_safe);

    RUN_TEST(test_build_response_layout_and_crc);
    RUN_TEST(test_build_response_buffer_too_small);

    RUN_TEST(test_process_request_rejected_when_window_closed);
    RUN_TEST(test_process_request_completes_pairing_when_window_open);
    RUN_TEST(test_process_request_malformed_frame_sends_nothing);

    RUN_TEST(test_init_opens_window_on_cold_boot);
    RUN_TEST(test_tick_button_hold_opens_window);
    RUN_TEST(test_tick_short_button_press_does_not_open_window);
    RUN_TEST(test_tick_closes_window_after_deadline);

    return UNITY_END();
}
