#include "lib/config.h"
#include "ch32v30x.h"
#include "lib/telemetry/telemetry.h"
#include "meshcore/meshframing.h"
#include "meshcore/packets/ack.h"
#include "meshcore/packets/advert.h"
#include "meshcore/packetstructs.h"
#include "meshcore/stats.h"
#include "util/hexdump.h"
#include "util/log.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "encrypted.h"
#include "lib/adc/temperature.h"
#include "lib/rtc/rtc.h"
#include "sx1262.h"

#define TAG "EncryptedMessage"

void sendEncryptedFrame (const NodeEntry *targetNode, uint8_t payloadType, const uint8_t *plain, size_t plainLen) {
    FrameStruct frame;
    memset(&frame, 0, sizeof(frame));
    size_t offset = 0;

    // 1. Header
    frame.header =
        (targetNode->path.pathLen > 0 ? ROUTE_TYPE_DIRECT : ROUTE_TYPE_FLOOD) |  // currently flood
        payloadType |
        PAYLOAD_VERSION_0;

    // 2. Destination + source
    frame.payload[offset++] = targetNode->pubKey[0];
    frame.payload[offset++] = persistent.pubkey[0];

    // 4. Encrypt + MAC
    size_t encLen;
    encrypt_then_mac (
        targetNode->secret,
        32,
        plain,
        plainLen,
        frame.payload + offset,
        &encLen);

    MESH_LOGD(TAG, "Plain len: %d, enc len: %d", plainLen, encLen);

    offset += encLen;

    // 5. Finalize
    frame.payloadLen = offset;
    memcpy (&frame.path, &targetNode->path, sizeof (frame.path));

    hexdump ("Encrypted frame", frame.payload, frame.payloadLen);
    LoRaTransmit (&frame);
    MESH_LOGD (TAG, "Encrypted frame tx finish\n");
}

void sendEncryptedTextMessage (const NodeEntry *targetNode, const PlainTextMessagePayload *msg) {
    if (targetNode == NULL) {
        MESH_LOGW (TAG, "Node is null");
        return;
    }
    if (targetNode->last_seen_lt == 0) {
        MESH_LOGW (TAG, "Node is not populated");
        return;
    }
    uint8_t buf[256];
    uint8_t index = 0;

    uint8_t msgLen = strlen (msg->message) + 1;
    buf[index++] = msg->timestamp;
    buf[index++] = msg->timestamp >> 8;
    buf[index++] = msg->timestamp >> 16;
    buf[index++] = msg->timestamp >> 24;

    buf[index++] = (msg->textType << 2) | (msg->attempt & 0x03);
    memcpy (&buf[index], msg->message, msgLen);
    index += msgLen;

    sendEncryptedFrame (
        targetNode,
        PAYLOAD_TYPE_TXT_MSG,
        buf,
        index);
}

void sendEncryptedResponse (const NodeEntry *targetNode, const Response *resp) {
    uint8_t buf[256];
    uint8_t index = 0;

    buf[index++] = (resp->tag) & 0xFF;
    buf[index++] = (resp->tag >> 8) & 0xFF;
    buf[index++] = (resp->tag >> 16) & 0xFF;
    buf[index++] = (resp->tag >> 24) & 0xFF;

    memcpy (&(buf[index]), resp->data, resp->dataLen);
    index += resp->dataLen;

    sendEncryptedFrame (
        targetNode,
        PAYLOAD_TYPE_RESPONSE,
        buf,
        index);
}

void sendEncryptedRequest (const NodeEntry *targetNode, const Request *req) {
    uint8_t buf[256];
    uint8_t index = 0;

    buf[index++] = req->timestamp;
    buf[index++] = req->timestamp >> 8;
    buf[index++] = req->timestamp >> 16;
    buf[index++] = req->timestamp >> 24;

    buf[index++] = req->requestType;
    memcpy (&(buf[index]), req->data, req->dataLen);
    index += req->dataLen;

    sendEncryptedFrame (
        targetNode,
        PAYLOAD_TYPE_REQ,
        buf,
        index);
}

void sendEncryptedPathPayload (const NodeEntry *targetNode, const ReturnedPathPayload *path) {
    uint8_t buf[256];
    uint8_t index = 0;

    buf[index++] = path->path.pathLen;
    memcpy (&buf[index], path->path.path, path->path.pathLen);
    index += path->path.pathLen;

    if (path->extra.dataLen > 0) {
        buf[index++] = path->extra.type;
        memcpy (&buf[index], path->extra.data, path->extra.dataLen);
    } else {
        buf[index++] = 0xFF;
        uint32_t timestamp = RTC_GetCounter();
        buf[index++] = timestamp;
        buf[index++] = timestamp >> 8;
        buf[index++] = timestamp >> 16;
        buf[index++] = timestamp >> 24;
    }

    sendEncryptedFrame (
        targetNode,
        PAYLOAD_TYPE_PATH,
        buf,
        index);
}

void printRequest (const Request *req) {
    iprintf ("Request:\n");
    iprintf ("  Timestamp: %u\n", req->timestamp);
    iprintf ("  Type: 0x%02X\n", req->requestType);
    iprintf ("  Data: ");
    hexdump ("  Data", req->data, req->dataLen);
}

void printResponse (const Response *resp) {
    iprintf ("Response:\n");
    iprintf ("  Tag: %u\n", resp->tag);
    iprintf ("  Data: ");
    hexdump ("  Data", resp->data, resp->dataLen);
}

void printPlainTextMessage (const PlainTextMessagePayload *msg) {
    iprintf ("PlainTextMessage:\n");
    iprintf ("  Timestamp: %u\n", msg->timestamp);
    iprintf ("  Attempt: %u\n", msg->attempt);
    iprintf ("  TextType: %u\n", msg->textType);
    iprintf ("  Message: %.*s\n", (int)strlen (msg->message), msg->message);
}

void printReturnedPathPayload (const ReturnedPathPayload *path) {
    iprintf ("ReturnedPathPayload:\n");
    iprintf ("  Path Length: %u\n", path->path.pathLen);
    iprintf ("  Path: ");
    hexdump ("  Path:", path->path.path, path->path.pathLen);
    iprintf ("  Extra Type: %u\n", path->extra.type);
    iprintf ("  Extra Data: ");
    hexdump ("  Extra data:", path->extra.data, path->extra.dataLen);
}

void printEncryptedPayload (const EncryptedPayloadStruct *enc) {
    iprintf ("EncryptedPayload:\n");
    iprintf ("  Type: 0x%02X\n", enc->type);
    iprintf ("  DestinationHash: 0x%02X\n", enc->destinationHash);
    iprintf ("  SourceHash: 0x%02X\n", enc->sourceHash);
    iprintf ("  CipherMAC: 0x%04X\n", enc->cipherMAC);
    iprintf ("  PayloadLen: %zu\n", enc->payloadLen);
    iprintf ("  Payload: ");
    for (size_t i = 0; i < enc->payloadLen; i++) {
        iprintf ("%02X ", enc->payload[i]);
    }
    iprintf ("\n");
}

void decodeEncryptedPayload (const FrameStruct *frame) {
    EncryptedPayloadStruct enc;
    memset (&enc, 0, sizeof (enc));
    enc.path = &(frame->path);
    enc.origFrame = frame;
    enc.type = frame->header & PAYLOAD_TYPE_MASK;
    unsigned char index = 0;

    enc.destinationHash = frame->payload[index++];
    enc.sourceHash = frame->payload[index++];
    enc.cipherMAC = frame->payload[index];
    enc.cipherMAC |= frame->payload[index + 1] << 8;

    if (enc.destinationHash != persistent.pubkey[0]) {
        return;
    }

    MESH_LOGI (TAG, "Finding remote node, sourceHash is %d", enc.sourceHash);

    NodeEntry *remNode = getNode (enc.sourceHash);

    enc.remNode = remNode;

    if (remNode == NULL) {
        MESH_LOGW (TAG, "Node not in DB");
        return;
    }
    remNode->last_seen_lt = RTC_GetCounter();
    MESH_LOGI (TAG, "Found node with index %d", remNode - persistent.contacts);


    if (mac_then_decrypt (remNode->secret, 32, &(frame->payload[index]), frame->payloadLen - index, enc.payload) != 0) {
        MESH_LOGW (TAG, "HMAC failed on encrypted message %s", remNode->name);
    } else {
        enc.payloadLen = frame->payloadLen - HMAC_SIZE;
        MESH_LOGI (TAG, "HMAC success from %s, %u bytes long", remNode->name, enc.payloadLen);
        sendDiscreteAck (enc.payload, 5 + strlen ((char *)&enc.payload[5]), remNode->pubKey);
    }

    iprintf ("  Typexdd: 0x%02X\n", enc.type);
    if (enc.payloadLen > 0) {
        parseEncryptedPayload (&enc);
    }
}

void sendPathBack (const NodeEntry *node, const Path *path) {
    ReturnedPathPayload retPath;
    retPath.extra.dataLen = 0;  // redo to send the resp in path
    retPath.extra.type = 0xFF;

    retPath.path.pathLen = path->pathLen;
    memcpy (retPath.path.path, path->path, path->pathLen);

    sendEncryptedPathPayload (node, &retPath);
}

void parseEncryptedPayload (const EncryptedPayloadStruct *enc) {
    // printEncryptedPayload(&enc);

    iprintf ("EncryptedPayload:\n");
    iprintf ("  Type: 0x%02X\n", enc->type);
    iprintf ("  DestinationHash: 0x%02X\n", enc->destinationHash);
    iprintf ("  SourceHash: 0x%02X\n", enc->sourceHash);
    iprintf ("  CipherMAC: 0x%04X\n", enc->cipherMAC);
    iprintf ("  PayloadLen: %u\n", enc->payloadLen);
    hexdump ("  Payload: ", enc->payload, enc->payloadLen);
    iprintf ("\n");

    uint8_t index = 0;
    if (enc->type == PAYLOAD_TYPE_PATH) {
        ReturnedPathPayload retPath;
        retPath.path.pathLen = enc->payload[index++];
        if (retPath.path.pathLen > 64) {
            MESH_LOGW (TAG, "Path too long\n");
            return;
        }
        memcpy (retPath.path.path, &(enc->payload[index]), retPath.path.pathLen);
        index += retPath.path.pathLen;
        retPath.extra.type = enc->payload[index++];
        retPath.extra.dataLen = enc->payloadLen - index;
        memcpy (retPath.extra.data, &(enc->payload[index]), retPath.extra.dataLen);

        if ((enc->origFrame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_FLOOD ||
            (enc->origFrame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) {
            sendPathBack (enc->remNode, enc->path);
        }

    } else if (enc->type == PAYLOAD_TYPE_REQ) {
        Request req;
        req.timestamp = enc->payload[index++];
        req.timestamp |= enc->payload[index++] << 8;
        req.timestamp |= enc->payload[index++] << 16;
        req.timestamp |= enc->payload[index++] << 24;
        enc->remNode->last_seen_rt = req.timestamp;
        req.requestType = enc->payload[index++];
        req.dataLen = enc->payloadLen - index;
        memcpy (req.data, &(enc->payload[index]), req.dataLen);
        printRequest (&req);
        if ((enc->origFrame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_FLOOD ||
            (enc->origFrame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) {
            sendPathBack (enc->remNode, enc->path);
        }
        switch (req.requestType) {
        case REQUEST_GET_STATS: {
            Response resp;
            resp.tag = RTC_GetCounter();
            stats.totalUpTimeSeconds = RTC_GetCounter() - startupTime;
            //stats.totalAirTimeSeconds = TICKS_TO_MS (tickAirtime / 1000);
            stats.totalAirTimeSeconds = 0;
            memcpy (resp.data, &stats, sizeof (stats));
            resp.dataLen = sizeof (stats);
            sendEncryptedResponse (enc->remNode, &resp);
            break;
        }
        case REQUEST_KEEPALIVE:
            break;
        case REQUEST_GET_TELEMETRY_DATA: {
            Response resp;
            resp.tag = req.timestamp;
            enc->remNode->last_seen_rt = req.timestamp;
            resp.dataLen = 0;
            resp.data[resp.dataLen++] = TELEM_CHANNEL_SELF;
            resp.data[resp.dataLen++] = LPP_TEMPERATURE;

            int16_t dataTemp = getDeciTemperature();

            resp.data[resp.dataLen++] = (dataTemp >> 8) & 0xFF;

            resp.data[resp.dataLen++] = dataTemp & 0xFF;

            resp.data[resp.dataLen++] = TELEM_CHANNEL_SELF;
            resp.data[resp.dataLen++] = LPP_VOLTAGE;

            int16_t dataVolt = stats.millivolts / 10;

            resp.data[resp.dataLen++] = (dataVolt >> 8) & 0xFF;

            resp.data[resp.dataLen++] = dataVolt & 0xFF;

            resp.data[resp.dataLen++] = 2;
            resp.data[resp.dataLen++] = LPP_VOLTAGE;

            dataVolt = 1973;

            resp.data[resp.dataLen++] = (dataVolt >> 8) & 0xFF;

            resp.data[resp.dataLen++] = dataVolt & 0xFF;

            if (enc->remNode->authenticated) {

                resp.data[resp.dataLen++] = 2;  // channel 2
                resp.data[resp.dataLen++] = LPP_TEMPERATURE;

                int16_t jokeTemp = 6942;

                resp.data[resp.dataLen++] = (jokeTemp >> 8) & 0xFF;

                resp.data[resp.dataLen++] = jokeTemp & 0xFF;


                encode_gps (TELEM_CHANNEL_SELF, persistent.latitude, persistent.longitude, persistent.altitude, &(resp.data[resp.dataLen]));
                // encode_gps(TELEM_CHANNEL_SELF, 48.1909f, 17.0303f, 234.0f, &(resp.data[resp.dataLen]));

                resp.dataLen += LPP_GPS_SIZE + 2;
            }


            sendEncryptedResponse (enc->remNode, &resp);

            iprintf ("Sent response, the temperature is %d decicelsius\n", dataTemp);

            break;
        }
        case REQUEST_GET_MIN_MAX_AVG:
            break;
        case REQUEST_GET_ACCESS_LIST:
            break;
        }

    } else if (enc->type == PAYLOAD_TYPE_RESPONSE) {
        Response resp;
        resp.tag = enc->payload[index++];
        resp.tag |= enc->payload[index++] << 8;
        resp.tag |= enc->payload[index++] << 16;
        resp.tag |= enc->payload[index++] << 24;
        resp.dataLen = enc->payloadLen - index;
        memcpy (resp.data, &(enc->payload[index]), resp.dataLen);
        printResponse (&resp);

    } else if (enc->type == PAYLOAD_TYPE_TXT_MSG) {
        PlainTextMessagePayload plaintext;
        plaintext.timestamp = enc->payload[index++];
        plaintext.timestamp |= enc->payload[index++] << 8;
        plaintext.timestamp |= enc->payload[index++] << 16;
        plaintext.timestamp |= enc->payload[index++] << 24;
        enc->remNode->last_seen_rt = plaintext.timestamp;
        plaintext.attempt = enc->payload[index] & 0x03;
        plaintext.textType = enc->payload[index++] >> 2;
        memcpy (plaintext.message, &(enc->payload[index]), enc->payloadLen - index);
        if ((enc->origFrame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_FLOOD ||
            (enc->origFrame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) {
            sendPathBack (enc->remNode, enc->path);
        }
        switch (plaintext.textType) {
        case TXT_TYPE_PLAIN:
            iprintf ("Plaintext message from %s, attempt %d, timestamp %d: %s", enc->remNode->name, plaintext.attempt, plaintext.timestamp, plaintext.message);
            break;

        case TXT_TYPE_CLI_DATA:
            if (enc->remNode->authenticated) {
                processCommand (plaintext.message, enc->remNode);
            }
            break;

        case TXT_TYPE_SIGNED_PLAIN: {
            uint8_t senderPubKeyPrefix[4];
            memcpy (senderPubKeyPrefix, plaintext.message, sizeof (senderPubKeyPrefix));
            NodeEntry *senderNode = getNodePrefix (senderPubKeyPrefix);
            iprintf ("Plaintext message from server %s, sender is %s, attempt %d, timestamp %d: %s", enc->remNode->name, senderNode->name, plaintext.attempt, plaintext.timestamp, &(plaintext.message[4]));
            break;
        }

        default:
            MESH_LOGW (TAG, "Unknown text type: %d", plaintext.textType);
            break;
        }
    }
}

// #define STR_EQ_LIT(s, lit) (memcmp ((s), (lit), sizeof (lit) - 1) == 0)
#define STR_EQ_LIT(s, lit) (strcmp (s, lit) == 0)

static int32_t parse_coord_micro(const char *s)
{
    int32_t sign = 1;
    int32_t int_part = 0;
    int32_t frac_part = 0;
    int32_t frac_div = 1;

    if (*s == '-') {
        sign = -1;
        s++;
    }

    // integer part
    while (*s >= '0' && *s <= '9') {
        int_part = int_part * 10 + (*s - '0');
        s++;
    }

    if (*s == '.') {
        s++;
        while (*s >= '0' && *s <= '9' && frac_div < 1000000) {
            frac_part = frac_part * 10 + (*s - '0');
            frac_div *= 10;
            s++;
        }
    }

    // scale to microdegrees
    while (frac_div < 1000000) {
        frac_part *= 10;
        frac_div *= 10;
    }

    return sign * (int_part * 1000000 + frac_part);
}

void processCommand (char *cmd, NodeEntry *remNode) {
    PlainTextMessagePayload replyPayload;
    replyPayload.timestamp = RTC_GetCounter();
    replyPayload.attempt = 0;
    replyPayload.textType = TXT_TYPE_CLI_DATA;

    uint8_t *reply = replyPayload.message;
    reply[0] = 0;

    while (*cmd == ' ') cmd++;  // skip leading spaces

    // Optional CLI prefix (xx|) for companion radio
    if (strlen (cmd) > 4 && cmd[2] == '|') {
        memcpy (reply, cmd, 3);
        reply += 3;
        cmd += 3;
    }

    /* ---------------- System ---------------- */
    if (STR_EQ_LIT (cmd, "reboot")) {
        NVIC_SystemReset();
    } else if (STR_EQ_LIT (cmd, "advert")) {
        sendAdvert (1);  // 1500ms delay in reference
        strcpy ((char *)reply, "OK - Advert sent");
    } else if (STR_EQ_LIT (cmd, "clear stats")) {
        memset (&stats, 0, sizeof (stats));
        strcpy ((char *)reply, "(OK - stats reset)");
    }

    else if (STR_EQ_LIT (cmd, "ver")) {
        sprintf ((char *)reply, "%s (Build: %s)", VERSION, __DATE__);
    }

    else if (STR_EQ_LIT (cmd, "board")) {
        sprintf ((char *)reply, "%s", BOARD);
    }

    /* ---------------- Clock ---------------- */
    else if (STR_EQ_LIT (cmd, "clock")) {
        RTC_Get();
        sprintf ((char *)reply, "%02d:%02d:%02d - %d/%d/%d UTC",
                 calendar.hour, calendar.min, calendar.sec,
                 calendar.w_date, calendar.w_month, calendar.w_year);
    } else if (STR_EQ_LIT (cmd, "time ")) {
        uint32_t secs = atoi (&cmd[5]);
        uint32_t curr = RTC_GetCounter();
        if (secs > curr) {
            RTC_SetCounter (secs);
            RTC_Get();
            sprintf ((char *)reply, "OK - clock set: %02d:%02d:%02d - %d/%d/%d UTC",
                     calendar.hour, calendar.min, calendar.sec,
                     calendar.w_date, calendar.w_month, calendar.w_year);
        } else {
            strcpy ((char *)reply, "(ERR: clock cannot go backwards)");
        }
    } else if (STR_EQ_LIT (cmd, "clock sync")) {
        uint32_t curr = RTC_GetCounter();
        uint32_t sender = replyPayload.timestamp;
        if (sender > curr) {
            RTC_SetCounter (sender + 1);
            RTC_Get();
            sprintf ((char *)reply, "OK - clock set: %02d:%02d:%02d - %d/%d/%d UTC",
                     calendar.hour, calendar.min, calendar.sec,
                     calendar.w_date, calendar.w_month, calendar.w_year);
        } else {
            strcpy ((char *)reply, "ERR: clock cannot go backwards");
        }
    }

    /*
    else if (STR_EQ_LIT (cmd, "tempradio ")) {
        char tmp[64];
        strcpy (tmp, &cmd[10]);

        const char *parts[5];
        int num = mesh_ParseTextParts (tmp, parts, 5);  // assume helper
        float freq = num > 0 ? strtof (parts[0], NULL) : 0.0f;
        float bw = num > 1 ? strtof (parts[1], NULL) : 0.0f;
        uint8_t sf = num > 2 ? atoi (parts[2]) : 0;
        uint8_t cr = num > 3 ? atoi (parts[3]) : 0;
        int timeout_mins = num > 4 ? atoi (parts[4]) : 0;
        if (freq >= 300.0f && freq <= 2500.0f && bw >= 7.0f && bw <= 500.0f &&
            sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && timeout_mins > 0) {
            Callbacks_ApplyTempRadioParams (freq, bw, sf, cr, timeout_mins);

            LoraApply();
            sprintf ((char *)reply, "OK - temp params for %d mins", timeout_mins);
        } else {
            strcpy ((char *)reply, "Error, invalid params");
        }
    }
    */

    /* ---------------- Password ---------------- */
    else if (STR_EQ_LIT (cmd, "password ")) {
        strncpy (persistent.password, &cmd[9], sizeof (persistent.password));
        // savePrefs(); TODO add this
        sprintf ((char *)reply, "password now: %s", persistent.password);
    }

    /* ---------------- GET / SET Config ---------------- */
    else if (STR_EQ_LIT (cmd, "get ")) {
        const char *config = &cmd[4];

/*
        if (memcmp (config, "af", 2) == 0) {
            sprintf (reply, "> %f", persistent.airtimeFactor);
            */
            /*
        } else if (memcmp (config, "int.thresh", 10) == 0) {
            sprintf (reply, "> %d", (uint32_t)_prefs->interference_threshold);
        } else if (memcmp (config, "agc.reset.interval", 18) == 0) {
            sprintf (reply, "> %d", ((uint32_t)_prefs->agc_reset_interval) * 4);
        } else if (memcmp (config, "multi.acks", 10) == 0) {
            sprintf (reply, "> %d", (uint32_t)_prefs->multi_acks);
        } else if (memcmp (config, "allow.read.only", 15) == 0) {
            sprintf (reply, "> %s", _prefs->allow_read_only ? "on" : "off");
        } else if (memcmp (config, "flood.advert.interval", 21) == 0) {
            sprintf (reply, "> %d", (uint32_t)_prefs->flood_advert_interval);
        } else if (memcmp (config, "advert.interval", 15) == 0) {
            sprintf (reply, "> %d", ((uint32_t)_prefs->advert_interval) * 2);1
            */
        //} else 
        if (memcmp (config, "guest.password", 14) == 0) {
            sprintf (reply, "> %s", persistent.guestPassword);
        } else if (memcmp (config, "name", 4) == 0) {
            sprintf (reply, "> %s", persistent.nodeName);
        } else if (memcmp (config, "repeat", 6) == 0) {
            sprintf (reply, "> %s", persistent.doRepeat ? "on" : "off");
        } else if (memcmp (config, "lat", 3) == 0) {
            sprintf (reply, "> %d", persistent.latitude);
        } else if (memcmp (config, "lon", 3) == 0) {
            sprintf (reply, "> %d", persistent.longitude);
            /*
        } else if (memcmp (config, "radio", 5) == 0) {
            char freq[16], bw[16];
            snprintf(freq, sizeof(freq), "%lf", persistent.frequencyInHz / 1000000.0);
            snprintf(bw, sizeof(bw), "%lf", loraBwToFloat(persistent.bandwidth));
            sprintf (reply, "> %s,%s,%d,%d", freq, bw, persistent.spreadingFactor, persistent.codingRate + 4);
        } else if (memcmp (config, "rxdelay", 7) == 0) {
            sprintf (reply, "> %s", StrHelper::ftoa (_prefs->rx_delay_base));
        } else if (memcmp (config, "txdelay", 7) == 0) {
            sprintf (reply, "> %s", StrHelper::ftoa (_prefs->tx_delay_factor));
        } else if (memcmp (config, "flood.max", 9) == 0) {
            sprintf (reply, "> %d", (uint32_t)_prefs->flood_max);
        } else if (memcmp (config, "direct.txdelay", 14) == 0) {
            sprintf (reply, "> %s", StrHelper::ftoa (_prefs->direct_tx_delay_factor));
        } else if (memcmp (config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) {
            sprintf (reply, "> %d", (uint32_t)_prefs->tx_power_dbm);
        } else if (memcmp (config, "freq", 4) == 0) {
            sprintf (reply, "> %s", StrHelper::ftoa (_prefs->freq));
            */
        } else if (memcmp (config, "public.key", 10) == 0) {
            strcpy (reply, "> ");
            hexdump_compact (persistent.pubkey, sizeof (persistent.pubkey), &(reply[2]), 70);
        } else if (memcmp (config, "role", 4) == 0) {
            sprintf (reply, "> %s", getStringRole (persistent.nodeType));
        } else if (memcmp (config, "adc.multiplier", 14) == 0) {
            sprintf (reply, "> %d.%d", persistent.adcMultiplier / 1000, persistent.adcMultiplier % 1000);
        } else {
            sprintf (reply, "??: %s", config);
        }

    } else if (STR_EQ_LIT (cmd, "set ")) {
        const char *config = &cmd[4];


/*
        if (memcmp (config, "af ", 3) == 0) {
            persistent.airtimeFactor = atof (&config[3]);
            // savePrefs();
            strcpy (reply, "OK");
            */
            /*
        } else if (memcmp (config, "int.thresh ", 11) == 0) {
            _prefs->interference_threshold = atoi (&config[11]);
            // savePrefs();
            strcpy (reply, "OK");
        } else if (memcmp (config, "agc.reset.interval ", 19) == 0) {
            _prefs->agc_reset_interval = atoi (&config[19]) / 4;
            // savePrefs();
            sprintf (reply, "OK - interval rounded to %d", ((uint32_t)_prefs->agc_reset_interval) * 4);
        } else if (memcmp (config, "multi.acks ", 11) == 0) {
            _prefs->multi_acks = atoi (&config[11]);
            // savePrefs();
            strcpy (reply, "OK");
            */
        //} else 
        if (memcmp (config, "allow.read.only ", 16) == 0) {
            if (memcmp (&config[16], "on", 2) == 0) {
                persistent.allowReadOnly = 1;
                strcpy (reply, "OK");
            } else if (memcmp (&config[16], "off", 3) == 0) {
                persistent.allowReadOnly = 0;
                strcpy (reply, "OK");
            }
            // savePrefs();
            /*
        } else if (memcmp (config, "flood.advert.interval ", 22) == 0) {
            int hours = _atoi (&config[22]);
            if ((hours > 0 && hours < 3) || (hours > 48)) {
                strcpy (reply, "Error: interval range is 3-48 hours");
            } else {
                _prefs->flood_advert_interval = (uint8_t)hours;
                _callbacks->updateFloodAdvertTimer();
                savePrefs();
                strcpy (reply, "OK");
            }
        } else if (memcmp (config, "advert.interval ", 16) == 0) {
            int mins = _atoi (&config[16]);
            if ((mins > 0 && mins < MIN_LOCAL_ADVERT_INTERVAL) || (mins > 240)) {
                sprintf (reply, "Error: interval range is %d-240 minutes", MIN_LOCAL_ADVERT_INTERVAL);
            } else {
                _prefs->advert_interval = (uint8_t)(mins / 2);
                _callbacks->updateAdvertTimer();
                savePrefs();
                strcpy (reply, "OK");
            }
            */
        } else if (memcmp (config, "guest.password ", 15) == 0) {
            strncpy (persistent.guestPassword, &config[15], sizeof (persistent.guestPassword));
            // savePrefs();
            strcpy (reply, "OK");
        } else if (memcmp (config, "name ", 5) == 0) {
            strncpy (persistent.nodeName, &config[5], sizeof (persistent.nodeName));
            // savePrefs();
            strcpy (reply, "OK");
        } else if (memcmp (config, "repeat ", 7) == 0) {
            if (memcmp (&config[7], "off", 3) == 0) {
                persistent.doRepeat = 0;
            } else if (memcmp (&config[7], "on", 2) == 0) {
                persistent.doRepeat = 1;
            }
            // savePrefs();
            strcpy (reply, persistent.doRepeat ? "OK - repeat is now ON" : "OK - repeat is now OFF");
            /*
        } else if (memcmp (config, "radio ", 6) == 0) {
            strcpy (tmp, &config[6]);
            const char *parts[4];
            int num = mesh::Utils::parseTextParts (tmp, parts, 4);
            float freq = num > 0 ? strtof (parts[0], 0) : 0.0f;
            float bw = num > 1 ? strtof (parts[1], 0) : 0.0f;
            uint8_t sf = num > 2 ? atoi (parts[2]) : 0;
            uint8_t cr = num > 3 ? atoi (parts[3]) : 0;
            if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) {
                _prefs->sf = sf;
                _prefs->cr = cr;
                _prefs->freq = freq;
                _prefs->bw = bw;
                _callbacks->savePrefs();
                strcpy (reply, "OK - reboot to apply");
            } else {
                strcpy (reply, "Error, invalid radio params");
            }
            */
        } else if (memcmp (config, "lat ", 4) == 0) {
            persistent.latitude = parse_coord_micro(&config[4]);
            // savePrefs();
            strcpy (reply, "OK");
        } else if (memcmp (config, "lon ", 4) == 0) {
            persistent.longitude = parse_coord_micro(&config[4]);
            // savePrefs();
            strcpy (reply, "OK");
            /*
        } else if (memcmp (config, "rxdelay ", 8) == 0) {
            float db = atof (&config[8]);
            if (db >= 0) {
                _prefs->rx_delay_base = db;
                savePrefs();
                strcpy (reply, "OK");
            } else {
                strcpy (reply, "Error, cannot be negative");
            }
        } else if (memcmp (config, "txdelay ", 8) == 0) {
            float f = atof (&config[8]);
            if (f >= 0) {
                _prefs->tx_delay_factor = f;
                savePrefs();
                strcpy (reply, "OK");
            } else {
                strcpy (reply, "Error, cannot be negative");
            }
        } else if (memcmp (config, "flood.max ", 10) == 0) {
            uint8_t m = atoi (&config[10]);
            if (m <= 64) {
                _prefs->flood_max = m;
                savePrefs();
                strcpy (reply, "OK");
            } else {
                strcpy (reply, "Error, max 64");
            }
        } else if (memcmp (config, "direct.txdelay ", 15) == 0) {
            float f = atof (&config[15]);
            if (f >= 0) {
                _prefs->direct_tx_delay_factor = f;
                savePrefs();
                strcpy (reply, "OK");
            } else {
                strcpy (reply, "Error, cannot be negative");
            }
        } else if (memcmp (config, "tx ", 3) == 0) {
            _prefs->tx_power_dbm = atoi (&config[3]);
            savePrefs();
            _callbacks->setTxPower (_prefs->tx_power_dbm);
            strcpy (reply, "OK");
        */
        /*
        } else if (memcmp (config, "freq ", 5) == 0) {
            double freq = atof (&config[5]);
            uint32_t newFreq = mhzToHzLimited (persistent.loraSettings.frequencyInHz);
            if (newFreq != 0) {
                persistent.loraSettings.frequencyInHz = newFreq;
            }
            // savePrefs();
            strcpy (reply, "OK - reboot to apply");
        */
        /*
        } else if (memcmp (config, "adc.multiplier ", 15) == 0) {
            persistent.adcMultiplier = atof (&config[15]);
            if (persistent.adcMultiplier == 0.0f) {
                strcpy (reply, "OK - using default board multiplier");
            } else {
                sprintf (reply, "OK - multiplier set to %.3f", persistent.adcMultiplier);
            }
            // savePrefs();
            */
        } else {
            sprintf (reply, "unknown config: %s", config);
        }
    }


    /* ---------------- Stats ---------------- */
    else if (STR_EQ_LIT (cmd, "stats-packets")) {
        sprintf (reply,
                 "{\"recv\":%u,\"sent\":%u,\"flood_tx\":%u,\"direct_tx\":%u,\"flood_rx\":%u,\"direct_rx\":%u}",
                 stats.packetsReceivedCount,
                 stats.packetsSentCount,
                 stats.sentFloodCount,
                 stats.sentDirectCount,
                 stats.receivedFloodCount,
                 stats.receivedDirectCount);
    } else if (STR_EQ_LIT (cmd, "stats-radio")) {
        sprintf (reply,
                 "{\"noise_floor\":%d,\"last_rssi\":%d,\"last_snr\":%d.00,\"tx_air_secs\":%u,\"rx_air_secs\":%u}",
                 stats.noiseFloor,
                 stats.lastRSSI,
                 stats.lastSNR / 4,
                 stats.totalAirTimeSeconds,
                 stats.total_rx_air_time_secs);
    } else if (STR_EQ_LIT (cmd, "stats-core")) {
        stats.totalUpTimeSeconds = RTC_GetCounter() - startupTime;
        //stats.totalAirTimeSeconds = TICKS_TO_MS (tickAirtime / 1000);
        stats.totalAirTimeSeconds = 0;
        sprintf (reply,
                 "{\"battery_mv\":%u,\"uptime_secs\":%u,\"errors\":%u,\"queue_len\":%u}",
                 getVoltage(),
                 stats.totalUpTimeSeconds,
                 stats.err_events,
                 stats.txQueueLength);
    }

    /* ---------------- Unknown ---------------- */
    else {
        strcpy ((char *)reply, "Unknown command");
    }

    sendEncryptedTextMessage (remNode, &replyPayload);
}
