// ============================================================================
// Native (host) unit tests — Batch A modules
//
// Covers:
//   - sensor_location: NVS-stubbed set/get round-trip
//   - jamming_detector: high-loss alarm, noise-floor-jump alarm
//
// Run with: pio test -e native -f test_batch_a
// ============================================================================

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

// ---------------------------------------------------------------------------
// Pull implementation units directly (same pattern as test_command_handler).
// UNIT_TEST is defined by platformio.ini for the native env.
// ---------------------------------------------------------------------------
#include "../../src/gps_module.cpp"     // native stub: gps_has_fix() always false
#include "../../src/sensor_location.cpp"
#include "../../src/jamming_detector.cpp"

// The native jamming_detector.cpp exposes this helper for time control
extern "C" void jamming_test_advance_ms(uint32_t ms);

// ============================================================================
// Module 1 — sensor_location tests
// ============================================================================

void test_location_init_defaults_zero() {
    // Fresh init on native env: location should be 0,0
    bool ok = location_init();
    TEST_ASSERT_TRUE(ok);

    int32_t lat = 99, lon = 99;
    location_get(&lat, &lon);
    TEST_ASSERT_EQUAL_INT32(0, lat);
    TEST_ASSERT_EQUAL_INT32(0, lon);
}

void test_location_roundtrip() {
    location_init();

    // Typical fixed-point coordinates
    int32_t lat_in =  389072000;  //  38.9072°  (Washington DC area)
    int32_t lon_in = -770369000;  // -77.0369°

    bool ok = location_set(lat_in, lon_in);
    TEST_ASSERT_TRUE(ok);

    int32_t lat_out = 0, lon_out = 0;
    location_get(&lat_out, &lon_out);

    TEST_ASSERT_EQUAL_INT32(lat_in, lat_out);
    TEST_ASSERT_EQUAL_INT32(lon_in, lon_out);
}

void test_location_overwrite() {
    location_init();

    location_set(100000000, -200000000);
    location_set(   500000,    1234567);

    int32_t lat = 0, lon = 0;
    location_get(&lat, &lon);

    TEST_ASSERT_EQUAL_INT32(   500000, lat);
    TEST_ASSERT_EQUAL_INT32(  1234567, lon);
}

void test_location_null_pointers_safe() {
    location_init();
    location_set(111, 222);
    // Must not crash when either pointer is null
    location_get(nullptr, nullptr);
    TEST_PASS();
}

// ============================================================================
// Module 3 — jamming_detector tests
// ============================================================================

// Helper: feed N observations with a given loss fraction
// channel=1, rssi=-70 (stable), packet_received_ok derived from loss_pct
static void feed_observations(uint16_t count, uint8_t loss_pct,
                               int16_t rssi = -70) {
    for (uint16_t i = 0; i < count; i++) {
        bool ok = !((i % 100) < loss_pct);  // simulate loss_pct% loss rate
        jamming_observe(1, rssi, ok);
        // Advance fake clock 1 ms per observation
        jamming_test_advance_ms(1);
    }
}

void test_jamming_init_no_alarm() {
    jamming_init();
    uint8_t reason = 99;
    bool alarmed = jamming_is_alarmed(&reason);
    TEST_ASSERT_FALSE(alarmed);
    TEST_ASSERT_EQUAL_UINT8(JAMMING_REASON_OK, reason);
}

void test_jamming_no_alarm_low_loss() {
    jamming_init();
    // 10% loss — well below the 70% threshold
    feed_observations(JAMMING_LOSS_WINDOW_SLOTS * 2, 10);

    uint8_t reason = 99;
    bool alarmed = jamming_is_alarmed(&reason);
    TEST_ASSERT_FALSE(alarmed);
    TEST_ASSERT_EQUAL_UINT8(JAMMING_REASON_OK, reason);
}

void test_jamming_high_loss_triggers() {
    jamming_init();
    // 90% loss — well above the 70% threshold
    feed_observations(JAMMING_LOSS_WINDOW_SLOTS * 2, 90);

    uint8_t reason = 0;
    bool alarmed = jamming_is_alarmed(&reason);
    TEST_ASSERT_TRUE(alarmed);
    TEST_ASSERT_EQUAL_UINT8(JAMMING_REASON_HIGH_LOSS, reason);
}

void test_jamming_noisefloor_jump() {
    jamming_init();

    // Seed a stable baseline at -85 dBm across all 13 channels
    // Use enough observations to get past the window fill requirement
    for (uint8_t i = 0; i < JAMMING_LOSS_WINDOW_SLOTS + 5; i++) {
        for (uint8_t ch = 1; ch <= JAMMING_WIFI_CHANNEL_COUNT; ch++) {
            jamming_observe(ch, -85, true);
            jamming_test_advance_ms(1);
        }
    }

    // Sanity: no alarm yet
    uint8_t reason = 0;
    TEST_ASSERT_FALSE(jamming_is_alarmed(&reason));

    // Now feed a sudden +30 dBm jump: -85 + 30 = -55
    // Feed on all channels to also keep std-dev low (rule out channel B)
    // We need enough observations so the window has low loss (rule out channel A)
    for (uint8_t ch = 1; ch <= JAMMING_WIFI_CHANNEL_COUNT; ch++) {
        jamming_observe(ch, -55, true);
        jamming_test_advance_ms(1);
    }

    // After the jump the current rssi (-55) vs baseline (~-85) is a delta of +30
    // which exactly meets JAMMING_NOISE_FLOOR_JUMP_DBM (>=30).
    reason = 0;
    bool alarmed = jamming_is_alarmed(&reason);
    TEST_ASSERT_TRUE(alarmed);
    TEST_ASSERT_EQUAL_UINT8(JAMMING_REASON_NOISE_FLOOR_JUMP, reason);
}

void test_jamming_spectrum_anomaly() {
    jamming_init();

    // Seed window to clear loss requirement (all good packets)
    for (uint8_t i = 0; i < JAMMING_LOSS_WINDOW_SLOTS + 5; i++) {
        jamming_observe(1, -70, true);
        jamming_test_advance_ms(1);
    }

    // Now deliver strongly divergent RSSI across channels:
    // odd channels at -40 dBm, even channels at -90 dBm → std-dev ≈ 25 dBm
    for (uint8_t rep = 0; rep < 3; rep++) {
        for (uint8_t ch = 1; ch <= JAMMING_WIFI_CHANNEL_COUNT; ch++) {
            int16_t rssi = (ch % 2 == 0) ? -90 : -40;
            jamming_observe(ch, rssi, true);
            jamming_test_advance_ms(1);
        }
    }

    uint8_t reason = 0;
    bool alarmed = jamming_is_alarmed(&reason);
    TEST_ASSERT_TRUE(alarmed);
    // Could be SPECTRUM_ANOMALY or NOISE_FLOOR_JUMP depending on which channel
    // triggers first — assert it's not OK.
    TEST_ASSERT_NOT_EQUAL(JAMMING_REASON_OK, reason);
}

// ============================================================================
// Unity lifecycle hooks (required by the Unity test framework)
// ============================================================================

void setUp(void) {
    // Reset both modules before each test case
    location_init();
    jamming_init();
}

void tearDown(void) {}

// ============================================================================
// Test runner
// ============================================================================

int main(int argc, char** argv) {
    (void)argc;
    (void)argv;

    UNITY_BEGIN();

    // Module 1 — sensor_location
    RUN_TEST(test_location_init_defaults_zero);
    RUN_TEST(test_location_roundtrip);
    RUN_TEST(test_location_overwrite);
    RUN_TEST(test_location_null_pointers_safe);

    // Module 3 — jamming_detector
    RUN_TEST(test_jamming_init_no_alarm);
    RUN_TEST(test_jamming_no_alarm_low_loss);
    RUN_TEST(test_jamming_high_loss_triggers);
    RUN_TEST(test_jamming_noisefloor_jump);
    RUN_TEST(test_jamming_spectrum_anomaly);

    return UNITY_END();
}
