// ============================================================================
// test/test_wifi_command_poll/test_wifi_command_poll.cpp
//
// Unity tests for src/wifi_command_poll.{h,cpp} -- the direct-WiFi inbound
// tasking transport adapter (JSON from OpenForge C2 -> CmdPacket ->
// dispatchCommand() -> AckPacket -> JSON result). Only the pure functions
// (no ARDUINO/HTTP dependency) are exercised here, same split as
// test_mythos_bridge and test_command_handler.
//
// Run with:
//   pio test -e native -f test_wifi_command_poll
// ============================================================================

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

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

// ── wcp_extract_command_id_type ─────────────────────────────────────────

void test_extract_id_type_from_real_poll_response(void) {
    const char* body =
        "{\"ok\":true,\"gateway_id\":\"Spencer01\",\"claimed\":true,\"count\":1,"
        "\"commands\":[{\"id\":\"ms5xyz-abc123\",\"type\":\"FOCUS_SENSORS\","
        "\"priority\":\"HIGH\",\"target_gateway_id\":\"Spencer01\","
        "\"params\":{\"cmd_type\":\"FOCUS_FREQ\",\"freq_mhz\":915.0}}]}";
    char id[40] = {0}, type[32] = {0};
    TEST_ASSERT_TRUE(wcp_extract_command_id_type(body, id, sizeof(id), type, sizeof(type)));
    TEST_ASSERT_EQUAL_STRING("ms5xyz-abc123", id);
    TEST_ASSERT_EQUAL_STRING("FOCUS_SENSORS", type);
}

void test_extract_returns_false_when_count_zero(void) {
    const char* body = "{\"ok\":true,\"count\":0,\"commands\":[]}";
    char id[40], type[32];
    TEST_ASSERT_FALSE(wcp_extract_command_id_type(body, id, sizeof(id), type, sizeof(type)));
}

void test_extract_returns_false_on_null_body(void) {
    char id[40], type[32];
    TEST_ASSERT_FALSE(wcp_extract_command_id_type(nullptr, id, sizeof(id), type, sizeof(type)));
}

// ── wcp_build_cmd_packet ─────────────────────────────────────────────────

void test_build_focus_freq_packet(void) {
    const char* body =
        "{\"type\":\"FOCUS_SENSORS\",\"params\":{\"cmd_type\":\"FOCUS_FREQ\","
        "\"freq_mhz\":915.5,\"duration_sec\":30,\"chan_hint\":2}}";
    CmdPacket pkt{};
    TEST_ASSERT_TRUE(wcp_build_cmd_packet(body, 0x1234, pkt));
    TEST_ASSERT_EQUAL_UINT8((uint8_t)SensorCmdType::FOCUS_FREQ, pkt.cmd_type);
    TEST_ASSERT_EQUAL_UINT8(6, pkt.param_len);
    TEST_ASSERT_EQUAL_UINT16(0x1234, pkt.target_sensor_id);
    float freq;
    memcpy(&freq, &pkt.params[0], sizeof(float));
    TEST_ASSERT_FLOAT_WITHIN(0.01f, 915.5f, freq);
    TEST_ASSERT_EQUAL_UINT8(30, pkt.params[4]);
    TEST_ASSERT_EQUAL_UINT8(2, pkt.params[5]);
}

void test_build_focus_freq_rejects_missing_required_field(void) {
    // freq_mhz is required -- no sensible default.
    const char* body = "{\"type\":\"FOCUS_SENSORS\",\"params\":{\"cmd_type\":\"FOCUS_FREQ\",\"duration_sec\":30}}";
    CmdPacket pkt{};
    TEST_ASSERT_FALSE(wcp_build_cmd_packet(body, 1, pkt));
}

void test_build_set_threshold_packet(void) {
    const char* body =
        "{\"type\":\"FOCUS_SENSORS\",\"params\":{\"cmd_type\":\"SET_THRESHOLD\","
        "\"model_id\":2,\"threshold_pct\":85}}";
    CmdPacket pkt{};
    TEST_ASSERT_TRUE(wcp_build_cmd_packet(body, 1, pkt));
    TEST_ASSERT_EQUAL_UINT8((uint8_t)SensorCmdType::SET_THRESHOLD, pkt.cmd_type);
    TEST_ASSERT_EQUAL_UINT8(2, pkt.param_len);
    TEST_ASSERT_EQUAL_UINT8(2, pkt.params[0]);
    TEST_ASSERT_EQUAL_UINT8(85, pkt.params[1]);
}

void test_build_enter_stealth_via_set_stealth_wrapper(void) {
    const char* body = "{\"type\":\"SET_STEALTH\",\"params\":{\"enable\":true,\"tx_power_dbm\":5,\"duration_min\":10}}";
    CmdPacket pkt{};
    TEST_ASSERT_TRUE(wcp_build_cmd_packet(body, 1, pkt));
    TEST_ASSERT_EQUAL_UINT8((uint8_t)SensorCmdType::ENTER_STEALTH, pkt.cmd_type);
    TEST_ASSERT_EQUAL_UINT8(2, pkt.param_len);
    TEST_ASSERT_EQUAL_UINT8(5, pkt.params[0]);
    TEST_ASSERT_EQUAL_UINT8(10, pkt.params[1]);
}

void test_build_exit_stealth_via_set_stealth_wrapper(void) {
    const char* body = "{\"type\":\"SET_STEALTH\",\"params\":{\"enable\":false}}";
    CmdPacket pkt{};
    TEST_ASSERT_TRUE(wcp_build_cmd_packet(body, 1, pkt));
    TEST_ASSERT_EQUAL_UINT8((uint8_t)SensorCmdType::EXIT_STEALTH, pkt.cmd_type);
    TEST_ASSERT_EQUAL_UINT8(0, pkt.param_len);
}

void test_build_set_tx_power_packet(void) {
    const char* body = "{\"type\":\"FOCUS_SENSORS\",\"params\":{\"cmd_type\":\"SET_TX_POWER\",\"tx_power_dbm\":14}}";
    CmdPacket pkt{};
    TEST_ASSERT_TRUE(wcp_build_cmd_packet(body, 1, pkt));
    TEST_ASSERT_EQUAL_UINT8((uint8_t)SensorCmdType::SET_TX_POWER, pkt.cmd_type);
    TEST_ASSERT_EQUAL_UINT8(1, pkt.param_len);
    TEST_ASSERT_EQUAL_UINT8(14, pkt.params[0]);
}

void test_build_report_status_packet(void) {
    const char* body = "{\"type\":\"FOCUS_SENSORS\",\"params\":{\"cmd_type\":\"REPORT_STATUS\"}}";
    CmdPacket pkt{};
    TEST_ASSERT_TRUE(wcp_build_cmd_packet(body, 1, pkt));
    TEST_ASSERT_EQUAL_UINT8((uint8_t)SensorCmdType::REPORT_STATUS, pkt.cmd_type);
    TEST_ASSERT_EQUAL_UINT8(0, pkt.param_len);
}

void test_build_network_scan_dump_packet(void) {
    const char* body = "{\"type\":\"FOCUS_SENSORS\",\"params\":{\"cmd_type\":\"NETWORK_SCAN_DUMP\"}}";
    CmdPacket pkt{};
    TEST_ASSERT_TRUE(wcp_build_cmd_packet(body, 1, pkt));
    TEST_ASSERT_EQUAL_UINT8((uint8_t)SensorCmdType::NETWORK_SCAN_DUMP, pkt.cmd_type);
}

void test_build_rejects_unknown_top_level_type(void) {
    const char* body = "{\"type\":\"SCAN_NETWORK\",\"params\":{}}";
    CmdPacket pkt{};
    TEST_ASSERT_FALSE(wcp_build_cmd_packet(body, 1, pkt));
}

void test_build_rejects_unknown_cmd_type_inside_wrapper(void) {
    const char* body = "{\"type\":\"FOCUS_SENSORS\",\"params\":{\"cmd_type\":\"UPDATE_MODEL_SLOT\",\"slot_id\":1}}";
    CmdPacket pkt{};
    TEST_ASSERT_FALSE(wcp_build_cmd_packet(body, 1, pkt));
}

void test_build_handles_real_poll_response_wrapper(void) {
    // Full realistic response (commands[] array wrapper), not a bare object.
    const char* body =
        "{\"ok\":true,\"count\":1,\"commands\":[{\"id\":\"abc\",\"type\":\"FOCUS_SENSORS\","
        "\"params\":{\"cmd_type\":\"SET_TX_POWER\",\"tx_power_dbm\":20}}]}";
    CmdPacket pkt{};
    TEST_ASSERT_TRUE(wcp_build_cmd_packet(body, 1, pkt));
    TEST_ASSERT_EQUAL_UINT8((uint8_t)SensorCmdType::SET_TX_POWER, pkt.cmd_type);
    TEST_ASSERT_EQUAL_UINT8(20, pkt.params[0]);
}

// ── wcp_build_result_json ────────────────────────────────────────────────

void test_result_json_complete_on_ok_ack(void) {
    AckPacket ack{};
    ack.status = (uint8_t)CmdAckStatus::OK;
    ack.result_code = 0;
    char out[384];
    size_t n = wcp_build_result_json(ack, /*recognized=*/true, "Spencer01",
        1000, 1001, 1000, false, 0, 0, false, 0, 0, out, sizeof(out));
    TEST_ASSERT_TRUE(n > 0);
    TEST_ASSERT_TRUE(strstr(out, "\"status\":\"COMPLETE\"") != nullptr);
    TEST_ASSERT_TRUE(strstr(out, "\"gateway_id\":\"Spencer01\"") != nullptr);
    TEST_ASSERT_TRUE(strstr(out, "\"ack_status\":\"OK\"") != nullptr);
}

void test_result_json_failed_on_rejected_ack(void) {
    AckPacket ack{};
    ack.status = (uint8_t)CmdAckStatus::REJECTED;
    char out[384];
    size_t n = wcp_build_result_json(ack, true, "Spencer01",
        1000, 1001, 1000, false, 0, 0, false, 0, 0, out, sizeof(out));
    TEST_ASSERT_TRUE(n > 0);
    TEST_ASSERT_TRUE(strstr(out, "\"status\":\"FAILED\"") != nullptr);
    TEST_ASSERT_TRUE(strstr(out, "\"error\":") != nullptr);
}

void test_result_json_failed_when_unrecognized(void) {
    AckPacket ack{};
    char out[384];
    size_t n = wcp_build_result_json(ack, /*recognized=*/false, "Spencer01",
        1000, 1001, 1000, false, 0, 0, false, 0, 0, out, sizeof(out));
    TEST_ASSERT_TRUE(n > 0);
    TEST_ASSERT_TRUE(strstr(out, "\"status\":\"FAILED\"") != nullptr);
    TEST_ASSERT_TRUE(strstr(out, "unsupported command type") != nullptr);
}

void test_result_json_includes_status_extra_fields_when_requested(void) {
    AckPacket ack{};
    ack.status = (uint8_t)CmdAckStatus::OK;
    char out[384];
    size_t n = wcp_build_result_json(ack, true, "Spencer01",
        1000, 1001, 1000, /*have_status_extra=*/true,
        /*battery_pct=*/87, /*wifi_rssi=*/-62, /*stealth_active=*/true,
        /*uptime_s=*/12345, /*free_heap_bytes=*/98765, out, sizeof(out));
    TEST_ASSERT_TRUE(n > 0);
    TEST_ASSERT_TRUE(strstr(out, "\"battery_pct\":87") != nullptr);
    TEST_ASSERT_TRUE(strstr(out, "\"wifi_rssi_dbm\":-62") != nullptr);
    TEST_ASSERT_TRUE(strstr(out, "\"stealth_active\":true") != nullptr);
    TEST_ASSERT_TRUE(strstr(out, "\"uptime_s\":12345") != nullptr);
}

void test_result_json_omits_status_extra_fields_by_default(void) {
    AckPacket ack{};
    ack.status = (uint8_t)CmdAckStatus::OK;
    char out[384];
    size_t n = wcp_build_result_json(ack, true, "Spencer01",
        1000, 1001, 1000, false, 0, 0, false, 0, 0, out, sizeof(out));
    TEST_ASSERT_TRUE(n > 0);
    TEST_ASSERT_TRUE(strstr(out, "battery_pct") == nullptr);
}

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

    RUN_TEST(test_extract_id_type_from_real_poll_response);
    RUN_TEST(test_extract_returns_false_when_count_zero);
    RUN_TEST(test_extract_returns_false_on_null_body);

    RUN_TEST(test_build_focus_freq_packet);
    RUN_TEST(test_build_focus_freq_rejects_missing_required_field);
    RUN_TEST(test_build_set_threshold_packet);
    RUN_TEST(test_build_enter_stealth_via_set_stealth_wrapper);
    RUN_TEST(test_build_exit_stealth_via_set_stealth_wrapper);
    RUN_TEST(test_build_set_tx_power_packet);
    RUN_TEST(test_build_report_status_packet);
    RUN_TEST(test_build_network_scan_dump_packet);
    RUN_TEST(test_build_rejects_unknown_top_level_type);
    RUN_TEST(test_build_rejects_unknown_cmd_type_inside_wrapper);
    RUN_TEST(test_build_handles_real_poll_response_wrapper);

    RUN_TEST(test_result_json_complete_on_ok_ack);
    RUN_TEST(test_result_json_failed_on_rejected_ack);
    RUN_TEST(test_result_json_failed_when_unrecognized);
    RUN_TEST(test_result_json_includes_status_extra_fields_when_requested);
    RUN_TEST(test_result_json_omits_status_extra_fields_by_default);

    return UNITY_END();
}
