/*
 * client.c — XRPL Timestamp Client - Version April 2026
 *
 * Build:   make
 * Install: make install        (copies binary to /usr/local/bin)
 *
 * Usage:
 *   ./xrpl-timestamp stamp   <file>                stamp a file on the XRPL
 *   ./xrpl-timestamp verify  <file> <receipt.json> verify a file against its receipt
 *   ./xrpl-timestamp status  <job_id>              check job status
 *
 * Privacy model:
 *   The file NEVER leaves this machine.
 *   commitment = SHA256( SHA256(file) || nonce )
 *   Only the commitment is sent to the server — the file and nonce stay local.
 *   You can read every line of this source to verify that claim.
 *
 * Payment flow:
 *   1. POST /stamp  → server returns 402 + payment instructions
 *   2. Client prints the XRP payment details and polls /status
 *   3. Worker detects payment → job: PENDING_PAYMENT → QUEUED → DONE
 *   4. Client saves receipt.json when status = DONE
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <errno.h>

#include <unistd.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <curl/curl.h>
#include <qrencode.h>

/* ── Configuration ───────────────────────────────────────────────────────── */
#ifndef API_BASE
#define API_BASE "https://xrpltimestamp.com/api"
#endif

#define NONCE_LEN        32
#define HEX_LEN          65   /* 32 bytes → 64 hex chars + NUL */
#define POLL_INTERVAL_S  10   /* seconds between /status polls */
#define POLL_TIMEOUT_S   2100 /* 35 minutes — slightly beyond payment window */

/* ── Types ───────────────────────────────────────────────────────────────── */
typedef struct {
    char *data;
    size_t size;
} Buffer;

/* ── Helpers ─────────────────────────────────────────────────────────────── */
static size_t curl_write_cb(void *ptr, size_t sz, size_t nmemb, void *userdata) {
    Buffer *buf = userdata;
    size_t n = sz * nmemb;
    buf->data = realloc(buf->data, buf->size + n + 1);
    if (!buf->data) { perror("realloc"); exit(1); }
    memcpy(buf->data + buf->size, ptr, n);
    buf->size += n;
    buf->data[buf->size] = '\0';
    return n;
}

static void bytes_to_hex(const uint8_t *b, size_t len, char *out) {
    for (size_t i = 0; i < len; i++)
        sprintf(out + i * 2, "%02x", b[i]);
    out[len * 2] = '\0';
}

/* Quick-and-dirty JSON field extractor — avoids a full JSON library dep. */
static int json_get_str(const char *json, const char *key, char *out, size_t out_len) {
    char needle[128];
    snprintf(needle, sizeof(needle), "\"%s\"", key);
    const char *p = strstr(json, needle);
    if (!p) return 0;
    p += strlen(needle);
    while (*p == ' ' || *p == ':' || *p == ' ') p++;
    if (*p == '"') {
        p++;
        size_t i = 0;
        while (*p && *p != '"' && i < out_len - 1) out[i++] = *p++;
        out[i] = '\0';
        return 1;
    }
    /* numeric value */
    size_t i = 0;
    while (*p && *p != ',' && *p != '}' && *p != '\n' && i < out_len - 1)
        out[i++] = *p++;
    out[i] = '\0';
    return i > 0;
}

/* ── SHA-256 helpers ─────────────────────────────────────────────────────── */
static int sha256_file(const char *path, uint8_t out[32]) {
    FILE *f = fopen(path, "rb");
    if (!f) { perror(path); return 0; }
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
    if (!ctx) { fclose(f); return 0; }
    EVP_DigestInit_ex(ctx, EVP_sha256(), NULL);
    uint8_t buf[65536];
    size_t n;
    while ((n = fread(buf, 1, sizeof(buf), f)) > 0)
        EVP_DigestUpdate(ctx, buf, n);
    fclose(f);
    unsigned int outlen = 32;
    EVP_DigestFinal_ex(ctx, out, &outlen);
    EVP_MD_CTX_free(ctx);
    return 1;
}

static void sha256_bytes(const uint8_t *in, size_t len, uint8_t out[32]) {
    unsigned int outlen = 32;
    EVP_Digest(in, len, out, &outlen, EVP_sha256(), NULL);
}

/* commitment = SHA256(SHA256(file) || nonce) */
static int compute_commitment(const char *path,
                              uint8_t nonce[NONCE_LEN],
                              char hex_commitment[HEX_LEN],
                              char hex_nonce[HEX_LEN]) {
    uint8_t h1[32];
    if (!sha256_file(path, h1)) return 0;

    if (RAND_bytes(nonce, NONCE_LEN) != 1) {
        fprintf(stderr, "Error: cannot generate random nonce\n");
        return 0;
    }

    uint8_t concat[64];
    memcpy(concat,      h1,    32);
    memcpy(concat + 32, nonce, 32);

    uint8_t commitment[32];
    sha256_bytes(concat, 64, commitment);

    bytes_to_hex(commitment, 32, hex_commitment);
    bytes_to_hex(nonce,      32, hex_nonce);
    return 1;
}

/* ── HTTP helpers ────────────────────────────────────────────────────────── */
static long http_post(const char *url, const char *body,
                      Buffer *resp, long *http_code) {
    CURL *curl = curl_easy_init();
    if (!curl) return -1;

    struct curl_slist *hdrs = NULL;
    hdrs = curl_slist_append(hdrs, "Content-Type: application/json");

    resp->data = malloc(1); resp->data[0] = '\0'; resp->size = 0;

    curl_easy_setopt(curl, CURLOPT_URL,            url);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS,     body);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER,     hdrs);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,  curl_write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA,      resp);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT,        20L);

    CURLcode rc = curl_easy_perform(curl);
    if (rc == CURLE_OK)
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, http_code);

    curl_slist_free_all(hdrs);
    curl_easy_cleanup(curl);
    return rc;
}

static long http_get(const char *url, Buffer *resp, long *http_code) {
    CURL *curl = curl_easy_init();
    if (!curl) return -1;

    resp->data = malloc(1); resp->data[0] = '\0'; resp->size = 0;

    curl_easy_setopt(curl, CURLOPT_URL,           url);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_cb);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA,     resp);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT,       20L);

    CURLcode rc = curl_easy_perform(curl);
    if (rc == CURLE_OK)
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, http_code);

    curl_easy_cleanup(curl);
    return rc;
}

/* ── Save receipt to JSON file ───────────────────────────────────────────── */
static void save_receipt(const char *file_path, const char *hex_nonce,
                          const char *hex_commitment,
                          const char *xrpl_tx_hash) {
    char receipt_path[512];
    snprintf(receipt_path, sizeof(receipt_path), "%s.receipt.json", file_path);

    FILE *f = fopen(receipt_path, "w");
    if (!f) { perror(receipt_path); return; }

    /* Extract bare filename (no directory path) */
    const char *file_name = file_path;
    const char *p = file_path;
    while (*p) { if (*p == '/' || *p == '\\') file_name = p + 1; p++; }

    fprintf(f,
        "{\n"
        "  \"tx_hash\": \"%s\",\n"
        "  \"nonce\": \"%s\",\n"
        "  \"commitment\": \"%s\",\n"
        "  \"file_name\": \"%s\"\n"
        "}\n",
        xrpl_tx_hash, hex_nonce, hex_commitment, file_name
    );
    fclose(f);
    printf("Receipt saved: %s\n\n", receipt_path);
    printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
    printf("  NEXT STEP — Print your proof certificate\n");
    printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
    printf("  1. Open https://xrpltimestamp.com/certificate.html\n");
    printf("  2. Paste the contents of %s\n", receipt_path);
    printf("     into the receipt field, or load the file directly.\n");
    printf("  3. Print or save as PDF — keep it with your original file.\n");
    printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n");
}

/* ── Terminal QR code ────────────────────────────────────────────────────── */
/*
 * Renders a QR code in the terminal using Unicode half-block characters.
 * Two QR rows are packed into one terminal line:
 *   top=0 bot=0 → ' '   top=1 bot=0 → '▀'
 *   top=0 bot=1 → '▄'   top=1 bot=1 → '█'
 * A mandatory 4-module quiet zone is added on all sides.
 */
static void print_qr_terminal(const char *text) {
    QRcode *qr = QRcode_encodeString(text, 0, QR_ECLEVEL_M, QR_MODE_8, 1);
    if (!qr) { fprintf(stderr, "  (QR generation failed)\n"); return; }

    int w    = qr->width;
    int pad  = 4;           /* quiet zone in modules */
    int full = w + pad * 2; /* total width including quiet zone */

    /* iterate two rows at a time */
    for (int row = -pad; row < w + pad; row += 2) {
        printf("  "); /* left margin */
        for (int col = -pad; col < full - pad; col++) {
            int top = 0, bot = 0;
            if (row    >= 0 && row    < w && col >= 0 && col < w)
                top = qr->data[row * w + col] & 1;
            if (row+1  >= 0 && row+1  < w && col >= 0 && col < w)
                bot = qr->data[(row+1) * w + col] & 1;
            if      (!top && !bot) printf(" ");
            else if ( top && !bot) printf("▀");
            else if (!top &&  bot) printf("▄");
            else                   printf("█");
        }
        printf("\n");
    }
    QRcode_free(qr);
}

/* ── Command: stamp ──────────────────────────────────────────────────────── */
static int cmd_stamp(const char *file_path) {
    uint8_t nonce[NONCE_LEN];
    char hex_commitment[HEX_LEN];
    char hex_nonce[HEX_LEN];

    printf("Computing commitment for: %s\n", file_path);
    if (!compute_commitment(file_path, nonce, hex_commitment, hex_nonce))
        return 1;

    printf("Commitment : %s\n", hex_commitment);
    printf("Nonce      : %s  (keep secret — required for verification)\n\n", hex_nonce);

    /* POST /stamp */
    char url[512];
    snprintf(url, sizeof(url), "%s/stamp", API_BASE);

    char body[256];
    snprintf(body, sizeof(body), "{\"commitment\":\"%s\"}", hex_commitment);

    Buffer resp = {0};
    long http_code = 0;
    long rc = http_post(url, body, &resp, &http_code);

    if (rc != CURLE_OK) {
        fprintf(stderr, "Network error: %s\n", curl_easy_strerror((CURLcode)rc));
        free(resp.data);
        return 1;
    }

    if (http_code == 409) {
        fprintf(stderr, "Server: commitment already submitted.\n%s\n", resp.data);
        free(resp.data);
        return 1;
    }

    if (http_code != 402) {
        fprintf(stderr, "Unexpected HTTP %ld:\n%s\n", http_code, resp.data);
        free(resp.data);
        return 1;
    }

    /* ── Parse 402 payment instructions ─────────────────────────────────── */
    char job_id_str[32], address[64], dest_tag_str[16],
         amount_xrp[8], expires_at[32], memo[512];

    json_get_str(resp.data, "job_id",      job_id_str,   sizeof(job_id_str));
    json_get_str(resp.data, "address",     address,      sizeof(address));
    json_get_str(resp.data, "dest_tag",    dest_tag_str, sizeof(dest_tag_str));
    json_get_str(resp.data, "amount_xrp",  amount_xrp,   sizeof(amount_xrp));
    json_get_str(resp.data, "expires_at",  expires_at,   sizeof(expires_at));
    json_get_str(resp.data, "memo",        memo,         sizeof(memo));

    int job_id = atoi(job_id_str);
    free(resp.data);

    printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
    printf("  PAYMENT REQUIRED — Job #%d\n", job_id);
    printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
    printf("  Send:             %s XRP\n",  amount_xrp);
    printf("  To address:       %s\n",      address);
    printf("  Destination Tag:  %s  ← REQUIRED, do not omit!\n", dest_tag_str);
    printf("  Expires:          %s\n",      expires_at);
    printf("\n");
    printf("  %s\n", memo);
    printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n");

    /* ── Xaman QR code ───────────────────────────────────────────────────── */
    /* xumm deep link: pre-fills address, amount (in drops) and dest tag     */
    char xaman_url[512];
    snprintf(xaman_url, sizeof(xaman_url),
             "https://xumm.app/sign/?to=%s&amount=%s&dt=%s",
             address, amount_xrp, dest_tag_str);
    printf("  Scan with Xaman to pay (address, amount and tag pre-filled):\n\n");
    print_qr_terminal(xaman_url);
    printf("\n");
    printf("Waiting for payment confirmation (polls every %ds)...\n\n",
           POLL_INTERVAL_S);

    /* ── Poll /status until DONE, FAILED, or EXPIRED ────────────────────── */
    char status_url[512];
    snprintf(status_url, sizeof(status_url), "%s/status?job_id=%d", API_BASE, job_id);

    time_t deadline = time(NULL) + POLL_TIMEOUT_S;
    int payment_acknowledged = 0;

    while (time(NULL) < deadline) {
        sleep(POLL_INTERVAL_S);

        Buffer sresp = {0};
        long sc = 0;
        if (http_get(status_url, &sresp, &sc) != CURLE_OK || sc != 200) {
            printf("  [poll] HTTP %ld — retrying...\n", sc);
            free(sresp.data);
            continue;
        }

        char status[32], tx_hash[70];
        json_get_str(sresp.data, "status",       status,  sizeof(status));
        json_get_str(sresp.data, "xrpl_tx_hash", tx_hash, sizeof(tx_hash));

        if (strcmp(status, "PENDING_PAYMENT") == 0) {
            printf("  [poll] Waiting for payment...\n");

        } else if (strcmp(status, "QUEUED") == 0 ||
                   strcmp(status, "PROCESSING") == 0) {
            if (!payment_acknowledged) {
                printf("  [poll] Payment confirmed! Anchoring to XRPL...\n");
                payment_acknowledged = 1;
            }

        } else if (strcmp(status, "DONE") == 0) {
            printf("\n✓ Timestamped!\n");
            printf("  XRPL tx: %s\n", tx_hash);
            printf("  Explorer: https://livenet.xrpl.org/transactions/%s\n\n", tx_hash);
            save_receipt(file_path, hex_nonce, hex_commitment, tx_hash);
            free(sresp.data);
            return 0;

        } else if (strcmp(status, "EXPIRED") == 0) {
            fprintf(stderr, "\nJob expired — payment not received within the time window.\n");
            fprintf(stderr, "Run 'stamp' again to create a new job.\n");
            free(sresp.data);
            return 1;

        } else if (strcmp(status, "FAILED") == 0) {
            fprintf(stderr, "\nJob failed on the server side. Please contact support.\n");
            free(sresp.data);
            return 1;
        }

        free(sresp.data);
    }

    fprintf(stderr, "\nClient timeout waiting for completion.\n");
    fprintf(stderr, "Check status later: ./xrpl-timestamp status %d\n", job_id);
    return 1;
}

/* ── Command: status ─────────────────────────────────────────────────────── */
static int cmd_status(const char *job_id_str) {
    char url[512];
    snprintf(url, sizeof(url), "%s/status?job_id=%s", API_BASE, job_id_str);

    Buffer resp = {0};
    long http_code = 0;
    if (http_get(url, &resp, &http_code) != CURLE_OK || http_code != 200) {
        fprintf(stderr, "HTTP %ld: %s\n", http_code, resp.data ? resp.data : "");
        free(resp.data);
        return 1;
    }

    printf("%s\n", resp.data);
    free(resp.data);
    return 0;
}

/* ── Command: verify ─────────────────────────────────────────────────────── */
static int cmd_verify(const char *file_path, const char *receipt_path) {
    FILE *f = fopen(receipt_path, "r");
    if (!f) { perror(receipt_path); return 1; }
    char receipt_buf[4096] = {0};
    size_t nread = fread(receipt_buf, 1, sizeof(receipt_buf) - 1, f);
    fclose(f);
    if (nread == 0) { fprintf(stderr, "Error: could not read receipt file\n"); return 1; }

    char stored_commitment[HEX_LEN];
    char stored_nonce[HEX_LEN];
    char xrpl_tx_hash[70];
    json_get_str(receipt_buf, "commitment", stored_commitment, sizeof(stored_commitment));
    json_get_str(receipt_buf, "nonce",      stored_nonce,      sizeof(stored_nonce));
    json_get_str(receipt_buf, "tx_hash",    xrpl_tx_hash,      sizeof(xrpl_tx_hash));

    /* Recompute commitment from file + nonce */
    uint8_t h1[32];
    if (!sha256_file(file_path, h1)) return 1;

    /* Decode hex nonce */
    uint8_t nonce[NONCE_LEN];
    for (int i = 0; i < NONCE_LEN; i++) {
        unsigned int byte;
        sscanf(stored_nonce + i * 2, "%02x", &byte);
        nonce[i] = (uint8_t)byte;
    }

    uint8_t concat[64];
    memcpy(concat, h1, 32);
    memcpy(concat + 32, nonce, 32);

    uint8_t recomputed[32];
    sha256_bytes(concat, 64, recomputed);

    char hex_recomputed[HEX_LEN];
    bytes_to_hex(recomputed, 32, hex_recomputed);

    printf("Stored commitment  : %s\n", stored_commitment);
    printf("Recomputed         : %s\n", hex_recomputed);

    if (strcmp(stored_commitment, hex_recomputed) != 0) {
        printf("RESULT: MISMATCH — file may have been modified\n");
        return 1;
    }

    printf("RESULT: MATCH — commitment is valid\n");
    printf("XRPL tx: %s\n", xrpl_tx_hash);
    return 0;
}

/* ── main ────────────────────────────────────────────────────────────────── */
int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr,
            "XRPL Timestamp Client — https://xrpltimestamp.com\n\n"
            "Usage:\n"
            "  %s stamp   <file>                  compute commitment & submit for timestamping\n"
            "  %s verify  <file> <receipt.json>   verify file integrity against a receipt\n"
            "  %s status  <job_id>                check the status of a pending job\n\n"
            "Privacy: your file never leaves this machine.\n"
            "         Only SHA256(SHA256(file)||nonce) is transmitted.\n",
            argv[0], argv[0], argv[0]);
        return 1;
    }

    curl_global_init(CURL_GLOBAL_DEFAULT);

    int ret = 1;
    if      (strcmp(argv[1], "stamp")  == 0 && argc == 3)
        ret = cmd_stamp(argv[2]);
    else if (strcmp(argv[1], "verify") == 0 && argc == 4)
        ret = cmd_verify(argv[2], argv[3]);
    else if (strcmp(argv[1], "status") == 0 && argc == 3)
        ret = cmd_status(argv[2]);
    else {
        fprintf(stderr, "Unknown command or wrong number of arguments.\n");
        fprintf(stderr, "Run without arguments for usage.\n");
    }

    curl_global_cleanup();
    return ret;
}
