/*
 * make_test_pad.c — generate a small test OTP pad on a target directory.
 *
 * Bit-compatible with the `otp` project's pad format:
 *   - <chksum>.pad : raw random bytes, first 32 bytes are the "reserved header"
 *     (also used as the key to encrypt the checksum).
 *   - <chksum>.state : text file "offset=32\n" (32-byte header reserved).
 *   - chksum is the 64-hex-char XOR checksum as computed by
 *     otp/src/crypto.c:calculate_checksum (position-dependent XOR folded into
 *     32 buckets, then XORed with the first 32 pad bytes).
 *
 * Usage:
 *   make_test_pad <pad_dir> <size_bytes>
 *
 * Example:
 *   make_test_pad /media/user/USBDISK/pads 1048576
 *
 * Entropy source: /dev/urandom (local-entropy test path only — NOT for production
 * pads; production pads should use the `otp` CLI with keyboard/TRNG entropy or a
 * future hardware signer).
 */

#define _POSIX_C_SOURCE 200809L

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>

#define CHKSUM_HEX_LEN 64
#define CHKSUM_BIN_LEN 32
#define HEADER_RESERVED 32
#define BUF_SIZE (64 * 1024)

static int compute_checksum(const char *pad_path, char *checksum_hex) {
    FILE *file = fopen(pad_path, "rb");
    if (!file) {
        fprintf(stderr, "compute_checksum: cannot open %s: %s\n",
                pad_path, strerror(errno));
        return 1;
    }

    unsigned char checksum[CHKSUM_BIN_LEN];
    unsigned char buffer[BUF_SIZE];
    size_t bytes_read;
    size_t total_bytes = 0;

    memset(checksum, 0, CHKSUM_BIN_LEN);

    while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
        for (size_t i = 0; i < bytes_read; i++) {
            size_t pos = total_bytes + i;
            unsigned char bucket = (unsigned char)(pos % CHKSUM_BIN_LEN);
            checksum[bucket] ^= buffer[i] ^
                               (unsigned char)((pos >> 8) & 0xFF) ^
                               (unsigned char)((pos >> 16) & 0xFF) ^
                               (unsigned char)((pos >> 24) & 0xFF);
        }
        total_bytes += bytes_read;
    }
    fclose(file);

    /* XOR the checksum with the first 32 bytes of the pad (the "pad key"). */
    file = fopen(pad_path, "rb");
    if (!file) {
        fprintf(stderr, "compute_checksum: cannot reopen %s: %s\n",
                pad_path, strerror(errno));
        return 1;
    }
    unsigned char pad_key[CHKSUM_BIN_LEN];
    if (fread(pad_key, 1, CHKSUM_BIN_LEN, file) != CHKSUM_BIN_LEN) {
        fprintf(stderr, "compute_checksum: pad too small for header key\n");
        fclose(file);
        return 1;
    }
    fclose(file);

    unsigned char encrypted_checksum[CHKSUM_BIN_LEN];
    for (int i = 0; i < CHKSUM_BIN_LEN; i++) {
        encrypted_checksum[i] = checksum[i] ^ pad_key[i];
    }

    for (int i = 0; i < CHKSUM_BIN_LEN; i++) {
        sprintf(checksum_hex + (i * 2), "%02x", encrypted_checksum[i]);
    }
    checksum_hex[CHKSUM_HEX_LEN] = '\0';
    return 0;
}

static int write_random(const char *path, size_t size) {
    int urand = open("/dev/urandom", O_RDONLY);
    if (urand < 0) {
        fprintf(stderr, "cannot open /dev/urandom: %s\n", strerror(errno));
        return 1;
    }

    int out = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (out < 0) {
        fprintf(stderr, "cannot create %s: %s\n", path, strerror(errno));
        close(urand);
        return 1;
    }

    unsigned char buffer[BUF_SIZE];
    size_t written = 0;
    while (written < size) {
        size_t chunk = size - written;
        if (chunk > sizeof(buffer)) chunk = sizeof(buffer);

        ssize_t got = 0;
        while ((size_t)got < chunk) {
            ssize_t r = read(urand, buffer + got, chunk - (size_t)got);
            if (r < 0) {
                if (errno == EINTR) continue;
                fprintf(stderr, "read urandom failed: %s\n", strerror(errno));
                close(out);
                close(urand);
                return 1;
            }
            if (r == 0) {
                fprintf(stderr, "urandom EOF (unexpected)\n");
                close(out);
                close(urand);
                return 1;
            }
            got += r;
        }

        ssize_t put = 0;
        while (put < got) {
            ssize_t w = write(out, buffer + put, (size_t)got - (size_t)put);
            if (w < 0) {
                if (errno == EINTR) continue;
                fprintf(stderr, "write %s failed: %s\n", path, strerror(errno));
                close(out);
                close(urand);
                return 1;
            }
            put += w;
        }
        written += (size_t)got;
    }

    close(out);
    close(urand);
    return 0;
}

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <pad_dir> <size_bytes>\n", argv[0]);
        return 1;
    }

    const char *pad_dir = argv[1];
    size_t size = (size_t)strtoull(argv[2], NULL, 10);
    if (size < 64) {
        fprintf(stderr, "size must be at least 64 bytes\n");
        return 1;
    }

    /* Ensure pad_dir exists. */
    struct stat st;
    if (stat(pad_dir, &st) != 0) {
        if (mkdir(pad_dir, 0755) != 0) {
            fprintf(stderr, "cannot create %s: %s\n", pad_dir, strerror(errno));
            return 1;
        }
    } else if (!S_ISDIR(st.st_mode)) {
        fprintf(stderr, "%s exists but is not a directory\n", pad_dir);
        return 1;
    }

    /* Write the pad to a temporary name first, then rename by checksum. */
    char tmp_path[1024];
    snprintf(tmp_path, sizeof(tmp_path), "%s/.tmp_pad_XXXXXX", pad_dir);
    /* mkstemp would be cleaner, but we want a stable name for the rename. */
    int tfd = mkstemp(tmp_path);
    if (tfd < 0) {
        fprintf(stderr, "mkstemp failed: %s\n", strerror(errno));
        return 1;
    }
    close(tfd);

    if (write_random(tmp_path, size) != 0) {
        unlink(tmp_path);
        return 1;
    }

    char chksum[CHKSUM_HEX_LEN + 1];
    if (compute_checksum(tmp_path, chksum) != 0) {
        unlink(tmp_path);
        return 1;
    }

    char final_path[1024];
    char state_path[1024];
    snprintf(final_path, sizeof(final_path), "%s/%s.pad", pad_dir, chksum);
    snprintf(state_path, sizeof(state_path), "%s/%s.state", pad_dir, chksum);

    if (rename(tmp_path, final_path) != 0) {
        fprintf(stderr, "rename %s -> %s failed: %s\n",
                tmp_path, final_path, strerror(errno));
        unlink(tmp_path);
        return 1;
    }

    /* Write initial state file: offset=32 (header reserved). */
    FILE *state = fopen(state_path, "w");
    if (!state) {
        fprintf(stderr, "cannot create %s: %s\n", state_path, strerror(errno));
        return 1;
    }
    fprintf(state, "offset=%d\n", HEADER_RESERVED);
    fclose(state);

    printf("Created test pad:\n");
    printf("  pad:   %s\n", final_path);
    printf("  state: %s\n", state_path);
    printf("  size:  %zu bytes\n", size);
    printf("  chksum: %s\n", chksum);
    printf("  chksum prefix (16 chars): %.16s\n", chksum);
    return 0;
}
