// ============================================================================
// test_mesh_validate.cpp — structural plausibility gate for over-the-air
// DetectionEvents (src/mesh_validate.cpp).
//
// The rejection cases below are not hypothetical: every "phantom_*" test uses
// values actually observed in OpenForge C2 on 2026-08-02, ingested from foreign
// Meshtastic traffic that PDL firmware misparsed as drone contacts.
//
// The acceptance tests matter just as much. A validator that drops legitimate
// traffic is worse than none — in particular a GPS-less unit reporting 0,0 is
// CORRECT behavior, not corruption, and must always pass.
// ============================================================================

#include <unity.h>
#include <cstring>
#include "config.h"
#include "mesh_validate.h"

// The native env sets test_build_src = false, so src/ is not compiled into the
// test binary. Pull the implementation in as a single TU, matching the pattern
// already used by test_ffps / test_ble_odid / test_health_report.
#include "../../src/mesh_validate.cpp"

// A known-good decoded RID detection: real drone position, sane everything.
static DetectionEvent good_detection() {
    DetectionEvent e;
    memset(&e, 0, sizeof(e));
    e.msg_type      = MSG_TYPE_DETECTION;
    e.sensor_id     = 0x93AAEC02;
    e.timestamp_utc = 1785600248u;
    e.class_label   = CLASS_RID_BLE_DECODED;
    e.confidence    = 255;
    e.rssi          = -71;
    e.noise_floor   = -100;
    e.lat_fixed     = 386100000;    //  38.6100000
    e.lon_fixed     = -781700000;   // -78.1700000
    e.battery_pct   = 88;
    e.flags         = FLAG_PROMOTED;
    return e;
}

static void test_good_detection_accepted() {
    TEST_ASSERT_TRUE(mesh_event_is_plausible(good_detection()));
}

// A GPS-less V3 (Spencer03) legitimately reports lat=0/lon=0. This is the
// single most important acceptance case — rejecting it would silently drop
// every health report from any unit without a GPS module fitted.
static void test_zero_position_is_valid_no_gps_unit() {
    DetectionEvent e = good_detection();
    e.msg_type    = MSG_TYPE_HEALTH;
    e.class_label = 0;
    e.lat_fixed   = 0;
    e.lon_fixed   = 0;
    e.flags       = 0;
    TEST_ASSERT_TRUE(mesh_event_is_plausible(e));
}

static void test_all_carried_msg_types_accepted() {
    const uint8_t types[] = { MSG_TYPE_DETECTION, MSG_TYPE_CANDIDATE,
                              MSG_TYPE_CORROBORATE, MSG_TYPE_HEALTH,
                              MSG_TYPE_COMPOSITE };
    for (unsigned i = 0; i < sizeof(types) / sizeof(types[0]); i++) {
        DetectionEvent e = good_detection();
        e.msg_type = types[i];
        TEST_ASSERT_TRUE(mesh_event_is_plausible(e));
    }
}

// Health packets legitimately carry rssi = 0 before any samples are averaged
// (health_report.cpp:129) — the RSSI window must not exclude zero.
static void test_zero_rssi_accepted() {
    DetectionEvent e = good_detection();
    e.msg_type = MSG_TYPE_HEALTH;
    e.rssi     = 0;
    TEST_ASSERT_TRUE(mesh_event_is_plausible(e));
}

// ---------------------------------------------------------------------------
// Rejection: values observed live in C2
// ---------------------------------------------------------------------------

// Latitude 107.2987782 was stored against Spencer01 via the un-clamped health
// relay path. Latitude cannot exceed 90.
static void test_phantom_impossible_latitude_rejected() {
    DetectionEvent e = good_detection();
    e.msg_type  = MSG_TYPE_HEALTH;
    e.lat_fixed = 1072987782;
    e.lon_fixed = -149413304;
    TEST_ASSERT_FALSE(mesh_event_is_plausible(e));
}

// 0x577E6A04 at (20.8389635, 0) — both values are individually in range, so
// this is caught only by the zero-consistency rule.
static void test_phantom_lat_with_zero_lon_rejected() {
    DetectionEvent e = good_detection();
    e.lat_fixed = 208389635;
    e.lon_fixed = 0;
    TEST_ASSERT_FALSE(mesh_event_is_plausible(e));
}

// 0xD058D812 at (10.0663296, 0) — same shape, different frame.
static void test_phantom_second_observed_coordinate_rejected() {
    DetectionEvent e = good_detection();
    e.lat_fixed = 100663296;
    e.lon_fixed = 0;
    TEST_ASSERT_FALSE(mesh_event_is_plausible(e));
}

static void test_zero_lat_with_nonzero_lon_rejected() {
    DetectionEvent e = good_detection();
    e.lat_fixed = 0;
    e.lon_fixed = -781700000;
    TEST_ASSERT_FALSE(mesh_event_is_plausible(e));
}

// ---------------------------------------------------------------------------
// Rejection: structural
// ---------------------------------------------------------------------------

static void test_unknown_msg_type_rejected() {
    DetectionEvent e = good_detection();
    e.msg_type = 0x7E;   // a Meshtastic header byte, not a PDL type
    TEST_ASSERT_FALSE(mesh_event_is_plausible(e));
}

static void test_command_msg_type_rejected() {
    // Real PDL type, but it does NOT ride in the DetectionEvent layout.
    DetectionEvent e = good_detection();
    e.msg_type = MSG_TYPE_COMMAND;
    TEST_ASSERT_FALSE(mesh_event_is_plausible(e));
}

static void test_battery_over_100_rejected() {
    DetectionEvent e = good_detection();
    e.battery_pct = 217;
    TEST_ASSERT_FALSE(mesh_event_is_plausible(e));
}

static void test_battery_exactly_100_accepted() {
    DetectionEvent e = good_detection();
    e.battery_pct = 100;
    TEST_ASSERT_TRUE(mesh_event_is_plausible(e));
}

static void test_class_label_out_of_enum_rejected() {
    DetectionEvent e = good_detection();
    e.class_label = 42;
    TEST_ASSERT_FALSE(mesh_event_is_plausible(e));
}

static void test_undefined_flag_bits_rejected() {
    DetectionEvent e = good_detection();
    e.flags = 0x80;
    TEST_ASSERT_FALSE(mesh_event_is_plausible(e));
}

static void test_all_defined_flags_together_accepted() {
    DetectionEvent e = good_detection();
    e.flags = FLAG_PROMOTED | FLAG_BLUE_FORCE | FLAG_SUPPRESSED;
    TEST_ASSERT_TRUE(mesh_event_is_plausible(e));
}

static void test_absurd_rssi_rejected() {
    DetectionEvent e = good_detection();
    e.rssi = 12000;
    TEST_ASSERT_FALSE(mesh_event_is_plausible(e));
}

static void test_longitude_out_of_range_rejected() {
    DetectionEvent e = good_detection();
    e.lon_fixed = 1900000000;   // 190 degrees
    TEST_ASSERT_FALSE(mesh_event_is_plausible(e));
}

// Boundary: exactly +/-90 and +/-180 are real, valid coordinates.
static void test_coordinate_extremes_accepted() {
    DetectionEvent e = good_detection();
    e.lat_fixed = MESH_LAT_FIXED_MAX;
    e.lon_fixed = MESH_LON_FIXED_MAX;
    TEST_ASSERT_TRUE(mesh_event_is_plausible(e));

    e.lat_fixed = -MESH_LAT_FIXED_MAX;
    e.lon_fixed = -MESH_LON_FIXED_MAX;
    TEST_ASSERT_TRUE(mesh_event_is_plausible(e));
}

void setUp() {}
void tearDown() {}

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

    RUN_TEST(test_good_detection_accepted);
    RUN_TEST(test_zero_position_is_valid_no_gps_unit);
    RUN_TEST(test_all_carried_msg_types_accepted);
    RUN_TEST(test_zero_rssi_accepted);
    RUN_TEST(test_battery_exactly_100_accepted);
    RUN_TEST(test_all_defined_flags_together_accepted);
    RUN_TEST(test_coordinate_extremes_accepted);

    RUN_TEST(test_phantom_impossible_latitude_rejected);
    RUN_TEST(test_phantom_lat_with_zero_lon_rejected);
    RUN_TEST(test_phantom_second_observed_coordinate_rejected);
    RUN_TEST(test_zero_lat_with_nonzero_lon_rejected);
    RUN_TEST(test_unknown_msg_type_rejected);
    RUN_TEST(test_command_msg_type_rejected);
    RUN_TEST(test_battery_over_100_rejected);
    RUN_TEST(test_class_label_out_of_enum_rejected);
    RUN_TEST(test_undefined_flag_bits_rejected);
    RUN_TEST(test_absurd_rssi_rejected);
    RUN_TEST(test_longitude_out_of_range_rejected);

    return UNITY_END();
}
