// ============================================================================
// wifi_command_poll.cpp — see wifi_command_poll.h for design notes.
// ============================================================================

#include "wifi_command_poll.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>

// ── pure JSON field extraction (no ARDUINO dependency) ─────────────────────
//
// Same deliberate choice as mythos_bridge.cpp: hand-rolled strstr-based
// extraction of a small, known set of flat fields rather than a general
// JSON library. Every field this module reads is a top-level or
// single-level-nested flat value (string/number/bool) with a unique key
// name across the whole response body, so scanning the whole body for
// "\"key\":" is safe and doesn't need to isolate the params{} substring
// first -- confirmed none of these key names collide with any other field
// OpenForge C2's command record shape uses (id/type/priority/
// target_gateway_id/target_sensor_ids/issuer/issued_at/expires_at/
// require_ack/status/created_at/updated_at/claimed_at/started_at/
// completed_at/progress_pct/last_message/execution_time_ms/model_used/
// result_data/error/params).

static bool find_key(const char* body, const char* key, const char** value_start) {
    char pat[40];
    int n = snprintf(pat, sizeof(pat), "\"%s\":", key);
    if (n <= 0 || (size_t)n >= sizeof(pat)) return false;
    const char* p = strstr(body, pat);
    if (!p) return false;
    *value_start = p + n;
    return true;
}

static bool extract_str(const char* body, const char* key, char* out, size_t cap) {
    const char* v;
    if (!find_key(body, key, &v)) return false;
    if (*v != '"') return false;
    v++;
    size_t i = 0;
    while (v[i] && v[i] != '"' && i + 1 < cap) {
        out[i] = v[i];
        i++;
    }
    if (v[i] != '"') return false;  // unterminated / value too long
    out[i] = '\0';
    return true;
}

static bool extract_num(const char* body, const char* key, double* out) {
    const char* v;
    if (!find_key(body, key, &v)) return false;
    char* endp = nullptr;
    double val = strtod(v, &endp);
    if (endp == v) return false;
    *out = val;
    return true;
}

static bool extract_bool(const char* body, const char* key, bool* out) {
    const char* v;
    if (!find_key(body, key, &v)) return false;
    if (strncmp(v, "true", 4) == 0) { *out = true; return true; }
    if (strncmp(v, "false", 5) == 0) { *out = false; return true; }
    return false;
}

static void wr_le_float(uint8_t* dst, float value) {
    // Target (ESP32-S3) and every plausible host-test architecture
    // (x86_64/ARM64) are little-endian -- direct copy matches
    // rd_le_float()'s own documented assumption in command_handler.cpp.
    memcpy(dst, &value, sizeof(float));
}

bool wcp_extract_command_id_type(
    const char* body,
    char* out_id, size_t id_cap,
    char* out_type, size_t type_cap) {
    if (!body) return false;
    double count = 0;
    if (!extract_num(body, "count", &count) || count < 1) return false;
    const char* commands = strstr(body, "\"commands\":[");
    if (!commands) return false;
    // Only the first entry matters -- the poll loop always uses limit=1.
    if (!extract_str(commands, "id", out_id, id_cap)) return false;
    if (!extract_str(commands, "type", out_type, type_cap)) return false;
    return true;
}

bool wcp_build_cmd_packet(const char* body, uint16_t own_sensor_id, CmdPacket& out) {
    char top_type[32] = {0};
    {
        const char* v;
        if (!find_key(body, "type", &v) || *v != '"') return false;
        size_t i = 0;
        v++;
        while (v[i] && v[i] != '"' && i + 1 < sizeof(top_type)) { top_type[i] = v[i]; i++; }
        top_type[i] = '\0';
    }

    // The "commands":[...] wrapper, if present (real poll response), else
    // treat the whole body as already being the single command object
    // (lets tests pass a bare {"type":...,"params":{...}} directly).
    const char* obj = strstr(body, "\"commands\":[");
    if (!obj) obj = body;

    char sensor_type[32] = {0};
    bool have_enable = false, enable_val = false;

    if (strcmp(top_type, "FOCUS_SENSORS") == 0) {
        if (!extract_str(obj, "cmd_type", sensor_type, sizeof(sensor_type))) return false;
    } else if (strcmp(top_type, "SET_STEALTH") == 0) {
        if (!extract_bool(obj, "enable", &enable_val)) return false;
        have_enable = true;
        strncpy(sensor_type, enable_val ? "ENTER_STEALTH" : "EXIT_STEALTH", sizeof(sensor_type) - 1);
    } else {
        return false;  // unsupported top-level type for direct WiFi tasking
    }
    (void)have_enable;

    out.target_sensor_id = own_sensor_id;
    out.cmd_id = 0;
    out.flags = 0;
    memset(out.params, 0, sizeof(out.params));

    if (strcmp(sensor_type, "FOCUS_FREQ") == 0) {
        double freq_mhz = 0, duration_sec = 0, chan_hint = 0;
        if (!extract_num(obj, "freq_mhz", &freq_mhz)) return false;
        extract_num(obj, "duration_sec", &duration_sec);   // optional, defaults 0
        extract_num(obj, "chan_hint", &chan_hint);         // optional, defaults 0
        out.cmd_type = (uint8_t)SensorCmdType::FOCUS_FREQ;
        wr_le_float(&out.params[0], (float)freq_mhz);
        out.params[4] = (uint8_t)duration_sec;
        out.params[5] = (uint8_t)chan_hint;
        out.param_len = 6;
    } else if (strcmp(sensor_type, "SET_THRESHOLD") == 0) {
        double model_id = 0, threshold_pct = 0;
        extract_num(obj, "model_id", &model_id);           // optional, defaults 0
        if (!extract_num(obj, "threshold_pct", &threshold_pct)) return false;
        out.cmd_type = (uint8_t)SensorCmdType::SET_THRESHOLD;
        out.params[0] = (uint8_t)model_id;
        out.params[1] = (uint8_t)threshold_pct;
        out.param_len = 2;
    } else if (strcmp(sensor_type, "ENTER_STEALTH") == 0) {
        double tx_power_dbm = 0, duration_min = 0;
        if (!extract_num(obj, "tx_power_dbm", &tx_power_dbm)) return false;
        extract_num(obj, "duration_min", &duration_min);   // optional, defaults 0
        out.cmd_type = (uint8_t)SensorCmdType::ENTER_STEALTH;
        out.params[0] = (uint8_t)tx_power_dbm;
        out.params[1] = (uint8_t)duration_min;
        out.param_len = 2;
    } else if (strcmp(sensor_type, "EXIT_STEALTH") == 0) {
        out.cmd_type = (uint8_t)SensorCmdType::EXIT_STEALTH;
        out.param_len = 0;
    } else if (strcmp(sensor_type, "SET_TX_POWER") == 0) {
        double dbm = 0;
        if (!extract_num(obj, "tx_power_dbm", &dbm)) return false;
        out.cmd_type = (uint8_t)SensorCmdType::SET_TX_POWER;
        out.params[0] = (uint8_t)dbm;
        out.param_len = 1;
    } else if (strcmp(sensor_type, "REPORT_STATUS") == 0) {
        out.cmd_type = (uint8_t)SensorCmdType::REPORT_STATUS;
        out.param_len = 0;
    } else if (strcmp(sensor_type, "NETWORK_SCAN_DUMP") == 0) {
        out.cmd_type = (uint8_t)SensorCmdType::NETWORK_SCAN_DUMP;
        out.param_len = 0;
    } else {
        return false;  // recognized wrapper, unsupported inner cmd_type
    }
    return true;
}

static const char* ack_status_str(uint8_t status) {
    switch ((CmdAckStatus)status) {
        case CmdAckStatus::OK:       return "OK";
        case CmdAckStatus::REJECTED: return "REJECTED";
        case CmdAckStatus::INVALID:  return "INVALID";
        case CmdAckStatus::FAILED:   return "FAILED";
        case CmdAckStatus::BUSY:     return "BUSY";
        default:                     return "UNKNOWN";
    }
}

size_t wcp_build_result_json(
    const AckPacket& ack, bool dispatch_recognized,
    const char* gateway_id,
    uint32_t started_at, uint32_t completed_at, uint32_t execution_time_ms,
    bool have_status_extra, uint8_t battery_pct, int8_t wifi_rssi,
    bool stealth_active, uint32_t uptime_s, uint32_t free_heap_bytes,
    char* out, size_t out_cap) {
    const bool ok = dispatch_recognized && ack.status == (uint8_t)CmdAckStatus::OK;

    char extra[160] = {0};
    if (have_status_extra) {
        snprintf(extra, sizeof(extra),
            ",\"battery_pct\":%u,\"wifi_rssi_dbm\":%d,\"stealth_active\":%s,"
            "\"uptime_s\":%u,\"free_heap_bytes\":%u",
            (unsigned)battery_pct, (int)wifi_rssi, stealth_active ? "true" : "false",
            (unsigned)uptime_s, (unsigned)free_heap_bytes);
    }

    int n = snprintf(out, out_cap,
        "{\"gateway_id\":\"%s\",\"status\":\"%s\","
        "\"started_at\":%u,\"completed_at\":%u,\"execution_time_ms\":%u,"
        "\"model_used\":\"t1-direct-wifi\","
        "\"result_data\":{\"ack_status\":\"%s\",\"result_code\":%u%s}%s%s}",
        gateway_id, ok ? "COMPLETE" : "FAILED",
        (unsigned)started_at, (unsigned)completed_at, (unsigned)execution_time_ms,
        dispatch_recognized ? ack_status_str(ack.status) : "UNRECOGNIZED",
        (unsigned)ack.result_code, extra,
        ok ? "" : ",\"error\":\"",
        ok ? "" : (dispatch_recognized ? "command rejected by sensor\"" : "unsupported command type\""));
    if (n <= 0 || (size_t)n >= out_cap) return 0;
    return (size_t)n;
}

#ifdef ARDUINO

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include "c2_wifi_bridge.h"
#include "health_report.h"

#define WCP_POLL_INTERVAL_MS 10000u
#define WCP_HTTP_TIMEOUT_MS  6000u

static uint16_t s_own_sensor_id = 0;
static bool     s_initialized = false;
static uint32_t s_last_poll_ms = 0;
static WifiCommandPollStats s_stats{};

void wifi_command_poll_init(uint16_t own_sensor_id) {
    s_own_sensor_id = own_sensor_id;
    s_initialized = true;
}

static bool http_get(const char* path, String& out_body) {
    if (!c2_wifi_bridge_is_configured()) return false;
    WiFiClientSecure client;
    client.setInsecure();
    HTTPClient http;
    char url[192];
    snprintf(url, sizeof(url), "https://%s:%u%s", c2_wifi_bridge_host(),
             (unsigned)c2_wifi_bridge_port(), path);
    if (!http.begin(client, url)) return false;
    http.setTimeout(WCP_HTTP_TIMEOUT_MS);
    http.addHeader("User-Agent",
        "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 "
        "(KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36");
    int code = http.GET();
    if (code == 200) out_body = http.getString();
    http.end();
    return code == 200;
}

static bool http_post(const char* path, const char* json) {
    if (!c2_wifi_bridge_is_configured()) return false;
    WiFiClientSecure client;
    client.setInsecure();
    HTTPClient http;
    char url[192];
    snprintf(url, sizeof(url), "https://%s:%u%s", c2_wifi_bridge_host(),
             (unsigned)c2_wifi_bridge_port(), path);
    if (!http.begin(client, url)) return false;
    http.setTimeout(WCP_HTTP_TIMEOUT_MS);
    http.addHeader("Content-Type", "application/json");
    http.addHeader("User-Agent",
        "Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 "
        "(KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36");
    int code = http.POST((uint8_t*)json, strlen(json));
    http.end();
    return code == 200;
}

void wifi_command_poll_tick() {
    if (!s_initialized || !c2_wifi_bridge_is_configured()) return;
    if (WiFi.status() != WL_CONNECTED) return;

    uint32_t now = millis();
    if (now - s_last_poll_ms < WCP_POLL_INTERVAL_MS) return;
    s_last_poll_ms = now;

    char path[128];
    snprintf(path, sizeof(path),
        "/api/sensor/commands?gateway_id=%s&claim=true&limit=1", SENSOR_NAME);

    String body;
    if (!http_get(path, body)) {
        s_stats.polls_failed++;
        return;
    }
    s_stats.polls_ok++;

    char cmd_id[40] = {0}, cmd_type[32] = {0};
    if (!wcp_extract_command_id_type(body.c_str(), cmd_id, sizeof(cmd_id), cmd_type, sizeof(cmd_type))) {
        return;  // nothing pending -- not an error
    }
    s_stats.commands_seen++;

    uint32_t started_at = (uint32_t)(millis() / 1000);  // no RTC dependency here;
                                                          // C2 only uses these for
                                                          // display/audit, not gating.
    CmdPacket pkt{};
    bool recognized = wcp_build_cmd_packet(body.c_str(), s_own_sensor_id, pkt);

    AckPacket ack{};
    ack.sensor_id = s_own_sensor_id;
    ack.status = (uint8_t)CmdAckStatus::OK;
    if (recognized) {
        dispatchCommand(pkt, ack);
        if (ack.status == (uint8_t)CmdAckStatus::OK) s_stats.commands_executed_ok++;
        else s_stats.commands_executed_failed++;
    } else {
        s_stats.commands_unsupported++;
        Serial.printf("[WifiCmdPoll] Unsupported command type for direct WiFi tasking: %s\n", cmd_type);
    }

    bool is_status = recognized && pkt.cmd_type == (uint8_t)SensorCmdType::REPORT_STATUS;
    uint32_t completed_at = (uint32_t)(millis() / 1000);

    char result_json[384];
    size_t n = wcp_build_result_json(
        ack, recognized, SENSOR_NAME,
        started_at, completed_at, (completed_at - started_at) * 1000u,
        is_status, health_read_battery_pct(), (int8_t)WiFi.RSSI(),
        stealthModeActive(), (uint32_t)(millis() / 1000), (uint32_t)ESP.getFreeHeap(),
        result_json, sizeof(result_json));
    if (n == 0) return;

    char result_path[64];
    snprintf(result_path, sizeof(result_path), "/api/sensor/command/%s/result", cmd_id);
    if (!http_post(result_path, result_json)) {
        Serial.printf("[WifiCmdPoll] Failed to POST result for command %s\n", cmd_id);
    }
}

void wifi_command_poll_get_stats(WifiCommandPollStats* out) {
    if (!out) return;
    *out = s_stats;
    out->configured = c2_wifi_bridge_is_configured();
}

#endif // ARDUINO
