#include "ch32v30x_rtc.h"
#include "lib/config.h"
#include "lib/monocypher/monocypher-ed25519.h"
#include "meshcore/meshframing.h"
#include "meshcore/packets/encrypted.h"
#include "meshcore/packetstructs.h"
#include "anonymous.h"
#include "util/hexdump.h"
#include <stdlib.h>

#define TAG "Anonymous"

void sendAnonymousRequest (const NodeEntry *targetNode, const uint8_t *password, uint32_t sync) {
    uint8_t passwordLen = strlen ((const char *)password);
    FrameStruct frame;
    frame.path.pathLen = 0;
    uint8_t offset = 0;

    // 1. Frame header
    frame.header = ((targetNode->path.pathLen > 0) ? ROUTE_TYPE_DIRECT : ROUTE_TYPE_FLOOD) | PAYLOAD_TYPE_ANON_REQ | PAYLOAD_VERSION_0;

    // 2. Payload header (unencrypted)
    frame.payload[offset++] = targetNode->pubKey[0];

    memcpy (frame.payload + offset, persistent.pubkey, 32);
    offset += 32;

    // 3. Build plaintext payload
    uint8_t plaintext[32];
    uint8_t p = 0;

    uint32_t last_seen_rt = RTC_GetCounter();
    plaintext[p++] = (last_seen_rt >> 0) & 0xFF;
    plaintext[p++] = (last_seen_rt >> 8) & 0xFF;
    plaintext[p++] = (last_seen_rt >> 16) & 0xFF;
    plaintext[p++] = (last_seen_rt >> 24) & 0xFF;

    if (targetNode->type == NODE_TYPE_ROOM_SERVER) {
        plaintext[p++] = (sync >> 0) & 0xFF;
        plaintext[p++] = (sync >> 8) & 0xFF;
        plaintext[p++] = (sync >> 16) & 0xFF;
        plaintext[p++] = (sync >> 24) & 0xFF;
    }

    if (passwordLen > 16) {
        passwordLen = 16;
    }
    memcpy (plaintext + p, password, passwordLen);
    p += passwordLen;

    size_t outputLen;
    // 4. Encrypt + MAC
    encrypt_then_mac (
        targetNode->secret,
        32,
        plaintext,
        p,
        frame.payload + offset,
        &outputLen);

    offset += outputLen;

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

    hexdump ("Anon payload", frame.payload, frame.payloadLen);

    LoRaTransmit (&frame);
}

void printAnonRequest (const AnonymousRequestPayload *req, int isRoomServer) {
    if (!req)
        return;

    iprintf ("AnonymousRequestPayload at %p\n", (void *)req);
    iprintf ("  destination hash: 0x%02X\n", req->destinationHash);

    iprintf ("  sender pubKey: ");
    for (int i = 0; i < sizeof (req->pubKey); i++) {
        iprintf ("%02X", req->pubKey[i]);
    }
    iprintf ("\n");

    iprintf ("  cipher MAC: 0x%04X\n", req->cipherMAC);

    iprintf ("  decrypted payload (%u bytes):\n", req->payloadLen);
    uint8_t index = 0;

    // timestamp (first 4 bytes)
    if (req->payloadLen >= 4) {
        uint32_t timestamp = req->payload[index++];
        timestamp |= req->payload[index++] << 8;
        timestamp |= req->payload[index++] << 16;
        timestamp |= req->payload[index++] << 24;
        iprintf ("    timestamp: %u\n", timestamp);
    }

    // room server sync timestamp
    if (isRoomServer && req->payloadLen >= index + 4) {
        uint32_t syncTimestamp = req->payload[index++];
        syncTimestamp |= req->payload[index++] << 8;
        syncTimestamp |= req->payload[index++] << 16;
        syncTimestamp |= req->payload[index++] << 24;
        iprintf ("    sync timestamp: %u\n", syncTimestamp);
    }


    // remaining bytes = password
    if (index < req->payloadLen) {
        uint8_t passwordLen = req->payloadLen - index;
        if (passwordLen > 16)
            passwordLen = 16;
        passwordLen = strnlen (&(req->payload[index]), passwordLen);
        iprintf ("    password: ");
        for (uint8_t i = 0; i < passwordLen; i++) {
            iprintf ("%c", req->payload[i + index]);
        }
        iprintf ("\n");
    }
}

size_t strnlen (const char *s, size_t maxLen) {
    size_t len = 0;
    while (len < maxLen && s[len] != '\0') {
        len++;
    }
    return len;
}

void decodeAnonReq (const FrameStruct *frame) {
    uint8_t index = 0;
    AnonymousRequestPayload anonReq;

    anonReq.destinationHash = frame->payload[index++];
    memcpy (anonReq.pubKey, &(frame->payload[index]), sizeof (anonReq.pubKey));
    index += sizeof (anonReq.pubKey);
    anonReq.cipherMAC = frame->payload[index];
    anonReq.cipherMAC |= frame->payload[index + 1] << 8;

    NodeEntry *foundNode = getNode (anonReq.pubKey[0]);

    if (foundNode == NULL) {
        foundNode = getNextNode();
        memset (foundNode, 0, sizeof (NodeEntry));

        strcpy (foundNode->name, "Anonymous node");
        foundNode->path.pathLen = 0;

        memcpy (foundNode->pubKey,
                anonReq.pubKey,
                sizeof (foundNode->pubKey));

        // --- X25519 conversion + DH ---
        uint8_t peer_x[32];

        crypto_eddsa_to_x25519 (peer_x, anonReq.pubKey);

        crypto_x25519 (foundNode->secret,
                       persistent.privkey,
                       peer_x);

        foundNode->gps_latitude = 0;
        foundNode->gps_longitude = 0;
        foundNode->type = 0;
        foundNode->last_seen_lt = RTC_GetCounter();

        MESH_LOGI (TAG, "New anonymous node created: %s", foundNode->name);
    } else {
        MESH_LOGD (TAG,
                   "Existing node found for pubKey[0]=0x%02X",
                   anonReq.pubKey[0]);
    }

    mac_then_decrypt (foundNode->secret, 32, &(frame->payload[index]), frame->payloadLen - index, anonReq.payload);
    anonReq.payloadLen = frame->payloadLen - index - 2;

    hexdump ("AnonReq payload", anonReq.payload, anonReq.payloadLen);

    uint8_t index2 = 0;
    foundNode->last_seen_rt = anonReq.payload[index2++];
    foundNode->last_seen_rt |= anonReq.payload[index2++] << 8;
    foundNode->last_seen_rt |= anonReq.payload[index2++] << 16;
    foundNode->last_seen_rt |= anonReq.payload[index2++] << 24;

    if (persistent.nodeType == NODE_TYPE_ROOM_SERVER) {
        foundNode->sync_timestamp = anonReq.payload[index2++];
        foundNode->sync_timestamp |= anonReq.payload[index2++] << 8;
        foundNode->sync_timestamp |= anonReq.payload[index2++] << 16;
        foundNode->sync_timestamp |= anonReq.payload[index2++] << 24;
    }

    printAnonRequest (&anonReq, persistent.nodeType == NODE_TYPE_ROOM_SERVER);

    uint8_t passwordLen = anonReq.payloadLen - index2;
    if (passwordLen > 16) {
        passwordLen = 16;
    }

    passwordLen = strnlen (&(anonReq.payload[index2]), passwordLen);

    MESH_LOGI (TAG, "Password len is %d.", passwordLen);
    uint8_t passwordBuf[16];
    memcpy (passwordBuf, &(anonReq.payload[index2]), passwordLen);

    if (memcmp (passwordBuf, persistent.password, passwordLen) == 0) {
        foundNode->authenticated = 1;
        MESH_LOGI (TAG, "Password correct, node %s authenticated.", foundNode->name);

        MESH_LOGI (TAG, "Login response sent to node %s.", foundNode->name);
    } else {
        MESH_LOGW (TAG, "Password incorrect for node %s.", foundNode->name);
    }

    Response resp;
    resp.tag = RTC_GetCounter();
    uint8_t index3 = 0;
    uint32_t randOut = rand();
    resp.data[index3++] = RESP_SERVER_LOGIN_OK;
    resp.data[index3++] = 0;                                                           // legacy
    resp.data[index3++] = foundNode->authenticated;                                    // isadmin
    resp.data[index3++] = foundNode->authenticated ? PERM_ACL_ADMIN : PERM_ACL_GUEST;  // permissions
    resp.data[index3++] = randOut & 0xFF;
    resp.data[index3++] = (randOut >> 8) & 0xFF;
    resp.data[index3++] = (randOut >> 16) & 0xFF;
    resp.data[index3++] = (randOut >> 24) & 0xFF;
    resp.data[index3++] = FIRMWARE_VER_LEVEL;
    resp.dataLen = index3;
    if ((frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_FLOOD ||
        (frame->header & ROUTE_TYPE_MASK) == ROUTE_TYPE_TRANSPORT_FLOOD) {
        sendPathBack (foundNode, &(frame->path));
    }
    sendEncryptedResponse (foundNode, &resp);
}
