// ============================================================================
// rnode_bridge.cpp
// ============================================================================

#include "rnode_bridge.h"
#include "radio_scheduler.h"
#include "reticulum_identity.h"
#include "config.h"

#include <cstring>
#include <cstdio>
#include <cctype>

#ifdef ARDUINO
#include <Arduino.h>
#include <nvs_flash.h>
#include <nvs.h>
#include <esp_system.h>
#include "mesh_publish.h"
#include "mesh_timesync.h"
#endif

static const char* TAG_RN = "RNode";

static bool s_rn_enabled = false;
static RnodeStats s_rn_stats;

// ---------------------------------------------------------------------------
// hex helpers
// ---------------------------------------------------------------------------
static int hex_nibble(char c) {
    if (c >= '0' && c <= '9') return c - '0';
    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
    return -1;
}

static int hex_to_bytes(const char* hex, uint8_t* out, size_t out_len) {
    size_t hex_len = strlen(hex);
    if (hex_len != out_len * 2) return -1;
    for (size_t i = 0; i < out_len; i++) {
        int hi = hex_nibble(hex[2*i]);
        int lo = hex_nibble(hex[2*i + 1]);
        if (hi < 0 || lo < 0) return -1;
        out[i] = (uint8_t)((hi << 4) | lo);
    }
    return 0;
}

// ---------------------------------------------------------------------------
// KISS encode/decode (RFC-style: FEND-framed, FESC-escaped)
// ---------------------------------------------------------------------------
static size_t kiss_encode(const uint8_t* in, size_t in_len,
                          uint8_t* out, size_t out_len) {
    size_t pos = 0;
    if (pos >= out_len) return 0;
    out[pos++] = KISS_FEND;
    if (pos >= out_len) return 0;
    out[pos++] = KISS_CMD_DATA;
    for (size_t i = 0; i < in_len; i++) {
        uint8_t b = in[i];
        if (b == KISS_FEND) {
            if (pos + 2 > out_len) return 0;
            out[pos++] = KISS_FESC;
            out[pos++] = KISS_TFEND;
        } else if (b == KISS_FESC) {
            if (pos + 2 > out_len) return 0;
            out[pos++] = KISS_FESC;
            out[pos++] = KISS_TFESC;
        } else {
            if (pos + 1 > out_len) return 0;
            out[pos++] = b;
        }
    }
    if (pos >= out_len) return 0;
    out[pos++] = KISS_FEND;
    return pos;
}

static size_t kiss_decode(const uint8_t* in, size_t in_len,
                          uint8_t* out, size_t out_len) {
    if (in_len < 3) return 0;
    if (in[0] != KISS_FEND) return 0;
    // expect cmd byte at [1]
    size_t i = 2;
    size_t pos = 0;
    while (i < in_len) {
        uint8_t b = in[i++];
        if (b == KISS_FEND) {
            return pos;  // end of frame
        } else if (b == KISS_FESC) {
            if (i >= in_len) return 0;
            uint8_t e = in[i++];
            if (e == KISS_TFEND) {
                if (pos >= out_len) return 0;
                out[pos++] = KISS_FEND;
            } else if (e == KISS_TFESC) {
                if (pos >= out_len) return 0;
                out[pos++] = KISS_FESC;
            } else {
                return 0;
            }
        } else {
            if (pos >= out_len) return 0;
            out[pos++] = b;
        }
    }
    return 0;  // missing trailing FEND
}

// ---------------------------------------------------------------------------
// Build L1 header + payload -- FIXED 2026-07-30, see rnode_bridge.h's
// RNODE_L1_HEADER_LEN comment for the real (flags, hops, dest_hash,
// context) layout this now matches, vs. the old (header_type, context,
// dest_hash) layout that misread every field.
// ---------------------------------------------------------------------------
static size_t build_l1(uint8_t flags, uint8_t hops, const uint8_t* dest_hash, uint8_t context,
                       const uint8_t* payload, size_t plen,
                       uint8_t* out, size_t out_len) {
    if (out_len < RNODE_L1_HEADER_LEN + plen) return 0;
    out[0] = flags;
    out[1] = hops;
    memcpy(&out[2], dest_hash, RNODE_DEST_HASH_LEN);
    out[18] = context;
    if (payload && plen) memcpy(&out[RNODE_L1_HEADER_LEN], payload, plen);
    return RNODE_L1_HEADER_LEN + plen;
}

// ---------------------------------------------------------------------------
// Public — radio params hook
// ---------------------------------------------------------------------------
int radio_rnode_set_params() {
#ifdef ARDUINO
    SX1262& r = mesh_get_radio();
    int st;
    st = r.setFrequency(RNODE_FREQ_MHZ);     if (st) return st;
    st = r.setBandwidth(RNODE_BW_KHZ);       if (st) return st;
    st = r.setSpreadingFactor(RNODE_SF);     if (st) return st;
    st = r.setCodingRate(RNODE_CR);          if (st) return st;
    st = r.setSyncWord(RNODE_SYNC);          if (st) return st;
    st = r.setPreambleLength(RNODE_PREAMBLE);if (st) return st;
    return 0;
#else
    return 0;
#endif
}

// ---------------------------------------------------------------------------
bool rnode_init() {
    memset(&s_rn_stats, 0, sizeof(s_rn_stats));
    s_rn_enabled = false;

#ifdef ARDUINO
    nvs_handle_t h;
    if (nvs_open(RNODE_NVS_NS, NVS_READONLY, &h) == ESP_OK) {
        uint8_t en = 0;
        if (nvs_get_u8(h, RNODE_NVS_ENABLED, &en) == ESP_OK && en == 1) {
            s_rn_enabled = true;
        }
        nvs_close(h);
    }
    Serial.printf("[%s] init enabled=%d\n", TAG_RN, s_rn_enabled ? 1 : 0);
#else
    s_rn_enabled = true;
#endif
    return s_rn_enabled;
}

bool rnode_is_rn_enabled() { return s_rn_enabled; }

// Enable/disable the Reticulum path at RUNTIME and persist the choice.
//
// rnode_init() reads RNODE_NVS_ENABLED once at boot into s_rn_enabled, and
// every entry point (rnode_announce, rnode_send_lxmf, rnode_handle_packet)
// early-returns on that flag. So writing the NVS key alone does nothing until
// the next reboot — which is exactly what happened when the /rnode-enable
// endpoint reported {"announced":false} on all five units. This sets the
// in-memory flag as well, so an announce works immediately.
void rnode_set_enabled(bool enabled) {
    s_rn_enabled = enabled;
#ifdef ARDUINO
    nvs_handle_t h;
    if (nvs_open(RNODE_NVS_NS, NVS_READWRITE, &h) == ESP_OK) {
        nvs_set_u8(h, RNODE_NVS_ENABLED, enabled ? 1 : 0);
        nvs_commit(h);
        nvs_close(h);
    }
    Serial.printf("[%s] runtime enable=%d (persisted)\n", TAG_RN, enabled ? 1 : 0);
#endif
}

void rnode_get_stats(RnodeStats* out) { if (out) *out = s_rn_stats; }

// ---------------------------------------------------------------------------
// Compose + emit a REAL RNS announce (see reticulum_identity.h) — FIXED
// 2026-07-30. The pure core (testable without ARDUINO's RNG/RTC) builds
// the announce + KISS frame; rnode_announce() below gathers real
// randomness/timestamp and transmits it.
// ---------------------------------------------------------------------------
size_t rnode_build_announce_frame(const char* app_name, const char* const* aspects, int n_aspects,
                                  const uint8_t* app_data, size_t app_data_len,
                                  const uint8_t random_hash10[10],
                                  uint8_t* out, size_t out_len) {
    uint8_t announce[RNS_ANNOUNCE_MAX_BYTES];
    size_t announce_len = rns_build_announce(rns_identity_get(), app_name, aspects, n_aspects,
                                             app_data, app_data_len, random_hash10,
                                             announce, sizeof(announce));
    if (announce_len == 0) return 0;
    return kiss_encode(announce, announce_len, out, out_len);
}

bool rnode_announce(const char* app_name, const char* const* aspects, int n_aspects,
                    const uint8_t* app_data, size_t app_data_len) {
    if (!s_rn_enabled || !app_name) return false;

    uint8_t random_hash[10];
#ifdef ARDUINO
    esp_fill_random(random_hash, 5);
    // RNS: random_hash[5:10] = int(unix_time).to_bytes(5, "big") -- a
    // 5-byte (40-bit) big-endian timestamp. Realistic unix timestamps fit
    // in 32 bits until year 2106, so the top byte is always 0 here.
    uint64_t now_ms = timesync_is_synced() ? timesync_now_utc_ms() : (uint64_t)millis();
    uint32_t now_s = (uint32_t)(now_ms / 1000);
    random_hash[5] = 0;
    random_hash[6] = (uint8_t)(now_s >> 24);
    random_hash[7] = (uint8_t)(now_s >> 16);
    random_hash[8] = (uint8_t)(now_s >> 8);
    random_hash[9] = (uint8_t)(now_s);
#else
    memset(random_hash, 0, sizeof(random_hash));
#endif

    uint8_t kiss_buf[RNODE_MAX_PACKET];
    size_t kiss_len = rnode_build_announce_frame(app_name, aspects, n_aspects,
                                                 app_data, app_data_len,
                                                 random_hash, kiss_buf, sizeof(kiss_buf));
    if (kiss_len == 0) { s_rn_stats.tx_failures++; return false; }

    if (!radio_sched_can_tx(RADIO_MODE_RNODE)) {
        if (!radio_sched_request_mode(RADIO_MODE_RNODE, 200)) {
            s_rn_stats.tx_failures++;
            return false;
        }
    }

#ifdef ARDUINO
    bool ok = mesh_publish_raw(kiss_buf, kiss_len);
    if (ok) { s_rn_stats.tx_announces++; return true; }
    s_rn_stats.tx_failures++;
    return false;
#else
    s_rn_stats.tx_announces++;
    return true;
#endif
}

// Sends a raw payload to a REMOTE destination hash -- unlike announce
// (self-referential), this genuinely needs an operator/caller-supplied
// destination, so the API here is unchanged. Still marked best-effort: a
// real encrypted LXMF-over-Link exchange isn't implemented, only the L1
// framing (see rnode_bridge.h's scope note).
bool rnode_send_lxmf(const char* dest_hash_hex,
                     const uint8_t* data, size_t len) {
    if (!s_rn_enabled || !dest_hash_hex || !data) return false;
    if (len > RNODE_MAX_PACKET - RNODE_L1_HEADER_LEN - 8) return false;

#ifdef ARDUINO
    Serial.printf("[%s] WARN lxmf TX is best-effort; not a real encrypted Link exchange\n", TAG_RN);
#endif

    uint8_t dh[RNODE_DEST_HASH_LEN];
    if (hex_to_bytes(dest_hash_hex, dh, RNODE_DEST_HASH_LEN) != 0) return false;

    uint8_t l1[RNODE_MAX_PACKET];
    size_t l1_len = build_l1(0x00, 0x00, dh, 0x01, data, len, l1, sizeof(l1));
    if (l1_len == 0) { s_rn_stats.tx_failures++; return false; }

    uint8_t kiss_buf[RNODE_MAX_PACKET];
    size_t kiss_len = kiss_encode(l1, l1_len, kiss_buf, sizeof(kiss_buf));
    if (kiss_len == 0) { s_rn_stats.tx_failures++; return false; }

    if (!radio_sched_can_tx(RADIO_MODE_RNODE)) {
        if (!radio_sched_request_mode(RADIO_MODE_RNODE, 300)) {
            s_rn_stats.tx_failures++;
            return false;
        }
    }

#ifdef ARDUINO
    bool ok = mesh_publish_raw(kiss_buf, kiss_len);
    if (ok) { s_rn_stats.tx_lxmf++; return true; }
    s_rn_stats.tx_failures++;
    return false;
#else
    s_rn_stats.tx_lxmf++;
    return true;
#endif
}

// ---------------------------------------------------------------------------
// Inbound RNode packet handler — kiss-decode, L1-parse, forward to PDL
// ---------------------------------------------------------------------------
int rnode_handle_packet(const uint8_t* buf, size_t len) {
    if (!s_rn_enabled) return -1;
    if (len < 2) return 0;

    // If the packet starts with KISS_FEND, decode; otherwise treat as raw L1
    uint8_t inner[RNODE_MAX_PACKET];
    size_t inner_len = 0;
    if (buf[0] == KISS_FEND) {
        inner_len = kiss_decode(buf, len, inner, sizeof(inner));
        if (inner_len == 0) {
            s_rn_stats.rx_decode_failures++;
            return 0;
        }
    } else {
        if (len > sizeof(inner)) return 0;
        memcpy(inner, buf, len);
        inner_len = len;
    }

    if (inner_len < RNODE_L1_HEADER_LEN) {
        s_rn_stats.rx_decode_failures++;
        return 0;
    }

    s_rn_stats.rx_packets++;

    // FIXED 2026-07-30: offsets now match the real 19-byte layout (see
    // RNODE_L1_HEADER_LEN's comment) -- this used to read inner[1] as
    // "context" (that's actually the hops byte) and start payload at
    // inner[18] (the real context byte, which got misread as the first
    // payload byte), silently shifting every field by one position for
    // any real RNS packet received.
    uint8_t  hops        = inner[1];
    const uint8_t* dhash = &inner[2];
    uint8_t  context     = inner[18];
    const uint8_t* payload = &inner[RNODE_L1_HEADER_LEN];
    size_t   plen       = inner_len - RNODE_L1_HEADER_LEN;
    (void)hops;

#ifdef ARDUINO
    // Wrap as MSG_TYPE_INTEROP_RX:
    //   [0]   = 0x21
    //   [1]   = source-protocol = 2 (RNode)
    //   [2]   = context byte
    //   [3..18] = destination hash (16 bytes)
    //   [19]  = payload length
    //   [20..] = payload (truncated to 200)
    uint8_t out_pkt[256];
    out_pkt[0] = MSG_TYPE_INTEROP_RX;
    out_pkt[1] = 2;
    out_pkt[2] = context;
    memcpy(&out_pkt[3], dhash, 16);
    size_t copy_len = plen > 200 ? 200 : plen;
    out_pkt[19] = (uint8_t)copy_len;
    memcpy(&out_pkt[20], payload, copy_len);
    extern void interop_router_enqueue_pdl(const uint8_t* p, size_t l);
    interop_router_enqueue_pdl(out_pkt, 20 + copy_len);
#else
    (void)context; (void)dhash; (void)payload; (void)plen;
#endif
    return (int)inner_len;
}

// ---------------------------------------------------------------------------
// Test helpers
// ---------------------------------------------------------------------------
#ifdef UNIT_TEST
extern "C" size_t rnode_test_kiss_encode(const uint8_t* in, size_t in_len,
                                         uint8_t* out, size_t out_len) {
    return kiss_encode(in, in_len, out, out_len);
}
extern "C" size_t rnode_test_kiss_decode(const uint8_t* in, size_t in_len,
                                         uint8_t* out, size_t out_len) {
    return kiss_decode(in, in_len, out, out_len);
}
extern "C" size_t rnode_test_build_l1(uint8_t flags, uint8_t hops,
                                      const uint8_t* dest_hash, uint8_t context,
                                      const uint8_t* payload, size_t plen,
                                      uint8_t* out, size_t out_len) {
    return build_l1(flags, hops, dest_hash, context, payload, plen, out, out_len);
}
#endif
