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

#include "mythos_bridge.h"
#include "config.h"   // NUM_CLASSES, ClassLabel

#include <cstring>
#include <cstdlib>

// ---------------------------------------------------------------------------
// Correction remap table -- module state, shared by both ARDUINO and native
// builds (unlike the HTTP polling below, this part has no Arduino
// dependency and must be testable). Identity-initialized so a sensor that
// has never received a correction behaves exactly as before.
// ---------------------------------------------------------------------------
namespace {
uint8_t  s_remap[NUM_CLASSES];
bool     s_remap_initialized = false;
uint32_t s_corrections_applied_total = 0;

void ensure_remap_initialized() {
    if (s_remap_initialized) return;
    for (uint8_t i = 0; i < NUM_CLASSES; i++) s_remap[i] = i;
    s_remap_initialized = true;
}

// Parses a run of ASCII digits starting at p (after skipping spaces).
// Returns true and fills *out on success; leaves *out untouched on failure.
bool parse_uint_at(const char* p, uint32_t* out) {
    while (*p == ' ') p++;
    if (*p < '0' || *p > '9') return false;
    uint32_t v = 0;
    while (*p >= '0' && *p <= '9') {
        v = v * 10 + (uint32_t)(*p - '0');
        p++;
    }
    *out = v;
    return true;
}
}  // namespace

// Pure, no radio/HTTP dependency -- always compiled (native and ARDUINO
// alike) so it's directly unit-testable without a real Mythos gateway.
uint32_t mythos_bridge_count_corrections(const char* body) {
    if (!body) return 0;
    uint32_t count = 0;
    const char* p = body;
    while ((p = strstr(p, "\"id\":")) != nullptr) {
        count++;
        p += 5;
    }
    return count;
}

uint32_t mythos_bridge_parse_and_apply_corrections(const char* body) {
    ensure_remap_initialized();
    if (!body) return 0;

    uint32_t applied = 0;
    const char* p = body;
    const char* kFrom = "\"label_from\":";
    const char* kTo   = "\"label_to\":";

    while ((p = strstr(p, kFrom)) != nullptr) {
        const char* from_pos = p + strlen(kFrom);
        uint32_t label_from = 0;
        if (!parse_uint_at(from_pos, &label_from)) {
            p = from_pos;
            continue;
        }
        // fed_gateway.py's add_correction() always emits label_to
        // immediately after label_from within the same correction object
        // (see mythos_bridge.h's design note), so the next "label_to":
        // marker anywhere after this point belongs to THIS correction.
        const char* to_marker = strstr(from_pos, kTo);
        if (!to_marker) break;
        uint32_t label_to = 0;
        if (!parse_uint_at(to_marker + strlen(kTo), &label_to)) {
            p = to_marker + strlen(kTo);
            continue;
        }
        if (label_from < NUM_CLASSES && label_to < NUM_CLASSES) {
            s_remap[label_from] = (uint8_t)label_to;
            applied++;
        }
        p = to_marker + strlen(kTo);
    }
    s_corrections_applied_total += applied;
    return applied;
}

uint8_t mythos_bridge_apply_correction(uint8_t raw_label) {
    ensure_remap_initialized();
    if (raw_label >= NUM_CLASSES) return raw_label;  // direct-decode classes pass through untouched
    return s_remap[raw_label];
}

#ifdef UNIT_TEST
void mythos_bridge_test_reset() {
    for (uint8_t i = 0; i < NUM_CLASSES; i++) s_remap[i] = i;
    s_remap_initialized = true;
    s_corrections_applied_total = 0;
}
#endif

#ifdef ARDUINO

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>

#define MYTHOS_POLL_INTERVAL_MS  15000u
#define MYTHOS_HTTP_TIMEOUT_MS   4000u

static char     s_host[64] = {0};
static uint16_t s_port = 0;
static bool     s_configured = false;
static uint32_t s_last_poll_ms = 0;
static MythosBridgeStats s_stats{};

void mythos_bridge_init() {
    // Inert until mythos_bridge_connect() is called (MYTHOS_CONNECT console cmd).
}

void mythos_bridge_connect(const char* host, uint16_t port) {
    strncpy(s_host, host, sizeof(s_host) - 1);
    s_host[sizeof(s_host) - 1] = '\0';
    s_port = port;
    s_configured = true;
    s_stats.configured = true;
    Serial.printf("[MythosBridge] Configured: %s:%u\n", s_host, (unsigned)s_port);
}

void mythos_bridge_tick() {
    if (!s_configured) return;
    if (WiFi.status() != WL_CONNECTED) return;
    uint32_t now = millis();
    if (now - s_last_poll_ms < MYTHOS_POLL_INTERVAL_MS) return;
    s_last_poll_ms = now;
    s_stats.last_poll_ms = now;

    HTTPClient http;
    char url[96];
    snprintf(url, sizeof(url), "http://%s:%u/api/fed/corrections/pending", s_host, (unsigned)s_port);
    http.begin(url);
    http.setTimeout(MYTHOS_HTTP_TIMEOUT_MS);

    int code = http.GET();
    s_stats.poll_count++;
    if (code == 200) {
        String body = http.getString();
        uint32_t seen = mythos_bridge_count_corrections(body.c_str());
        uint32_t applied = mythos_bridge_parse_and_apply_corrections(body.c_str());
        s_stats.corrections_seen_total += seen;
        s_stats.corrections_applied_total += applied;
        if (seen > 0) {
            Serial.printf("[MythosBridge] %lu pending correction(s) fetched, %lu applied to the local class-label map\n",
                          (unsigned long)seen, (unsigned long)applied);
        }
    } else {
        s_stats.poll_failures++;
        Serial.printf("[MythosBridge] poll failed, HTTP %d\n", code);
    }
    http.end();
}

void mythos_bridge_get_stats(MythosBridgeStats* out) {
    if (out) *out = s_stats;
}

#else  // !ARDUINO — native/test stub

void mythos_bridge_init() {}
void mythos_bridge_connect(const char*, uint16_t) {}
void mythos_bridge_tick() {}
void mythos_bridge_get_stats(MythosBridgeStats* out) {
    if (out) { MythosBridgeStats z{}; *out = z; }
}

#endif
