/*
 * vault1984-cli CLI — credential access for AI agents
 * Copyright (c) 2026 Vault1984. Elastic License 2.0.
 */

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

#include "keystore.h"
#include "http.h"
#include "jsbridge.h"
#include "util.h"
#include "../vendor/cjson/cJSON.h"
#include "bearssl.h"

#define VERSION "0.1.0"

static void usage(void) {
    fprintf(stderr,
        "vault1984-cli %s — credential access for AI agents\n"
        "\n"
        "Usage:\n"
        "  vault1984-cli --token <token> get <query>\n"
        "  vault1984-cli --token <token> list [filter]\n"
        "  vault1984-cli --token <token> totp <query>\n"
        "  vault1984-cli test-totp <base32-seed>\n"
        "  vault1984-cli test-crypto\n"
        "\n"
        "Options:\n"
        "  --token <t>  Agent token (from vault1984 web UI)\n"
        "  --json       Output as JSON\n"
        "  --help       Show this help\n"
        "  --version    Show version\n",
        VERSION);
}

/* --- URL + auth helpers --- */

static void build_url(char *buf, size_t len, const struct v84_config *cfg, const char *path) {
    snprintf(buf, len, "%s%s", cfg->vault_url, path);
}

static void get_bearer(const struct v84_config *cfg, char *buf, size_t len) {
    base64_encode(cfg->l2_key, 8, buf, len);  /* L1 = first 8 bytes */
}

/* --- test commands --- */

static int cmd_test_crypto(void) {
    fprintf(stderr, "vault1984-cli: crypto self-test\n");

    /* BearSSL AES-128-GCM roundtrip */
    {
        fprintf(stderr, "  [C] AES-128-GCM roundtrip... ");
        unsigned char key[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
        unsigned char nonce[12] = {0};
        unsigned char pt[] = "hello world test";
        unsigned char buf[sizeof(pt) + 16];
        unsigned char tag[16];
        memcpy(buf, pt, sizeof(pt));

        br_aes_ct64_ctr_keys aes_ctx;
        br_aes_ct64_ctr_init(&aes_ctx, key, 16);
        br_gcm_context gcm;
        br_gcm_init(&gcm, &aes_ctx.vtable, br_ghash_ctmul64);
        br_gcm_reset(&gcm, nonce, 12);
        br_gcm_flip(&gcm);
        br_gcm_run(&gcm, 1, buf, sizeof(pt));
        br_gcm_get_tag(&gcm, tag);

        br_gcm_reset(&gcm, nonce, 12);
        br_gcm_flip(&gcm);
        br_gcm_run(&gcm, 0, buf, sizeof(pt));
        if (!br_gcm_check_tag(&gcm, tag) || memcmp(buf, pt, sizeof(pt)) != 0) {
            fprintf(stderr, "FAIL\n"); return 1;
        }
        fprintf(stderr, "OK\n");
    }

    if (jsbridge_init() != 0) { fprintf(stderr, "FAIL: jsbridge_init\n"); return 1; }

    /* JS crypto tests */
    unsigned char test_key[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};

    { /* base64 roundtrip */
        char *r = jsbridge_eval(
            "var orig = new Uint8Array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]);"
            "var b64 = native_base64_encode(orig);"
            "var back = native_base64_decode(b64);"
            "var ok = (orig.length === back.length);"
            "for (var i=0; i<orig.length && ok; i++) ok = (orig[i] === back[i]);"
            "'base64 roundtrip: ' + (ok ? 'OK' : 'FAIL');");
        if (r) { fprintf(stderr, "  [JS] %s\n", r); free(r); }
    }
    { /* raw AES-GCM */
        char *r = jsbridge_eval(
            "var k = new Uint8Array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]);"
            "var pt = native_encode_utf8('hello');"
            "var ct = native_aes_gcm_encrypt(k, pt);"
            "var pt2 = native_aes_gcm_decrypt_blob(k, ct);"
            "var ok = (pt.length === pt2.length);"
            "for (var i=0; i<pt.length && ok; i++) ok = (pt[i] === pt2[i]);"
            "'raw AES-GCM: ' + (ok ? 'OK' : 'FAIL');");
        if (r) { fprintf(stderr, "  [JS] %s\n", r); free(r); }
    }
    { /* HKDF determinism */
        char *r = jsbridge_eval(
            "var ikm = new Uint8Array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]);"
            "var info = native_encode_utf8('test-info');"
            "var k1 = native_hkdf_sha256(ikm, null, info, 16);"
            "var k2 = native_hkdf_sha256(ikm, null, info, 16);"
            "var ok = true; for (var i=0; i<16; i++) ok = ok && (k1[i] === k2[i]);"
            "'HKDF determinism: ' + (ok ? 'OK' : 'FAIL');");
        if (r) { fprintf(stderr, "  [JS] %s\n", r); free(r); }
    }
    { /* L2 field roundtrip */
        char *r = jsbridge_eval(
            "var k = new Uint8Array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]);"
            "var ct = vault1984.crypto.l2_encrypt_field(k, 'abcdef01', 'pw', 'secret');"
            "var pt = vault1984.crypto.l2_decrypt_field(k, 'abcdef01', 'pw', ct);"
            "'l2 roundtrip: ' + (pt === 'secret' ? 'OK' : 'FAIL');");
        if (r) { fprintf(stderr, "  [JS] %s\n", r); free(r); }
    }

    /* L2 encrypt/decrypt via C API */
    fprintf(stderr, "  encrypting: \"s3cret-p@ssw0rd!\"\n");
    char *ct = jsbridge_encrypt_field(test_key, 16, "password", "s3cret-p@ssw0rd!");
    if (!ct) { fprintf(stderr, "FAIL: l2_encrypt\n"); jsbridge_cleanup(); return 1; }
    fprintf(stderr, "  ciphertext: %s\n", ct);

    char *pt = jsbridge_decrypt_field(test_key, 16, "password", ct);
    free(ct);
    if (!pt || strcmp(pt, "s3cret-p@ssw0rd!") != 0) {
        fprintf(stderr, "FAIL: l2_decrypt\n"); free(pt); jsbridge_cleanup(); return 1;
    }
    fprintf(stderr, "  decrypted:  \"%s\"\n", pt);
    free(pt);

    /* Wrong key rejection */
    char *ct2 = jsbridge_encrypt_field(test_key, 16, "pw", "test");
    if (ct2) {
        unsigned char wrong[16] = {0};
        char *bad = jsbridge_decrypt_field(wrong, 16, "pw", ct2);
        if (bad) { fprintf(stderr, "FAIL: wrong key accepted\n"); free(bad); free(ct2); jsbridge_cleanup(); return 1; }
        fprintf(stderr, "  wrong key rejection: OK\n");
        free(ct2);
    }

    /* TOTP RFC 6238 test vector */
    char *code = jsbridge_eval(
        "vault1984.totp.generate_totp('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 59, 30, 6);");
    if (!code || strcmp(code, "287082") != 0) {
        fprintf(stderr, "FAIL: TOTP (got %s, expect 287082)\n", code ? code : "null");
        free(code); jsbridge_cleanup(); return 1;
    }
    fprintf(stderr, "  TOTP RFC6238: OK (%s)\n", code);
    free(code);

    char *live = jsbridge_totp("GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ");
    if (live) { fprintf(stderr, "  TOTP live: %s\n", live); free(live); }

    jsbridge_cleanup();
    fprintf(stderr, "PASS: all tests passed\n");
    return 0;
}

/* --- main --- */

int main(int argc, char **argv) {
    if (argc < 2) { usage(); return 1; }

    const char *cmd = argv[1];

    /* No-config commands */
    if (strcmp(cmd, "--help") == 0 || strcmp(cmd, "-h") == 0) { usage(); return 0; }
    if (strcmp(cmd, "--version") == 0) { printf("vault1984-cli %s\n", VERSION); return 0; }
    if (strcmp(cmd, "test-crypto") == 0) { return cmd_test_crypto(); }
    if (strcmp(cmd, "test-roundtrip") == 0) {
        if (jsbridge_init() != 0) { fprintf(stderr, "error: crypto init failed\n"); return 1; }
        if (jsbridge_load("crypto/test_crypto.js") != 0) {
            fprintf(stderr, "error: cannot load test_crypto.js\n");
            jsbridge_cleanup();
            return 1;
        }
        char *r = jsbridge_eval("globalThis._v1984_test_result");
        if (r) { printf("%s\n", r); }
        int ok = (r && strstr(r, "FAILED") == NULL);
        free(r);
        jsbridge_cleanup();
        return ok ? 0 : 1;
    }
    if (strcmp(cmd, "eval") == 0 && argc > 2) {
        if (jsbridge_init() != 0) { fprintf(stderr, "error: crypto init failed\n"); return 1; }
        char *r = jsbridge_eval(argv[2]);
        if (r) { printf("%s\n", r); free(r); }
        jsbridge_cleanup();
        return r ? 0 : 1;
    }
    if (strcmp(cmd, "test-totp") == 0) {
        if (argc < 3) {
            fprintf(stderr, "usage: vault1984-cli test-totp <base32-seed>\n");
            return 1;
        }
        if (jsbridge_init() != 0) { fprintf(stderr, "error: crypto init failed\n"); return 1; }
        char *code = jsbridge_totp(argv[2]);
        if (code) {
            int remaining = 30 - (int)(time(NULL) % 30);
            printf("%s  expires in %ds\n", code, remaining);
            free(code);
        } else { fprintf(stderr, "error: TOTP failed\n"); }
        jsbridge_cleanup();
        return code ? 0 : 1;
    }

    /* Parse --token and find the command */
    const char *token_b64 = NULL;
    const char *command = NULL;
    int cmd_start = 0;
    int json_output = 0;

    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "--token") == 0 && i + 1 < argc) {
            token_b64 = argv[++i];
        } else if (strcmp(argv[i], "--json") == 0) {
            json_output = 1;
        } else if (argv[i][0] != '-' && !command) {
            command = argv[i];
            cmd_start = i + 1;
        }
    }

    if (!command) { fprintf(stderr, "error: no command\n"); usage(); return 1; }
    if (!token_b64) { fprintf(stderr, "error: --token required\n"); usage(); return 1; }

    /* Decode token: base64url → AES-128-GCM encrypted blob → decrypt → split */
    if (jsbridge_init() != 0) { fprintf(stderr, "error: crypto init failed\n"); return 1; }

    /* Decrypt token via JS (same key derivation as browser) */
    char js_code[2048];
    snprintf(js_code, sizeof(js_code),
        "(function() {"
        "  var seed = native_encode_utf8('vault1984-l2-');"
        "  var encKey = native_hkdf_sha256(seed, null, native_encode_utf8('token'), 16);"
        "  var ct = native_base64_decode('%s');"
        "  var pt = native_aes_gcm_decrypt_blob(encKey, ct);"
        "  return native_base64_encode(pt);"
        "})()", token_b64);
    char *decrypted_b64 = jsbridge_eval(js_code);
    if (!decrypted_b64) {
        fprintf(stderr, "error: token decryption failed (invalid or corrupted token)\n");
        jsbridge_cleanup();
        return 1;
    }

    unsigned char token_raw[512];
    size_t token_len = 0;
    if (base64_decode(decrypted_b64, token_raw, sizeof(token_raw), &token_len) != 0 || token_len < 3) {
        fprintf(stderr, "error: invalid token payload\n");
        free(decrypted_b64);
        jsbridge_cleanup();
        return 1;
    }
    free(decrypted_b64);

    /* Split on null bytes: host \0 agent_name \0 l2_key_16_bytes */
    char *vault_host = (char *)token_raw;
    char *sep1 = memchr(token_raw, '\0', token_len);
    if (!sep1 || (size_t)(sep1 - (char *)token_raw) >= token_len - 1) {
        fprintf(stderr, "error: malformed token\n");
        jsbridge_cleanup();
        return 1;
    }
    char *agent_name = sep1 + 1;
    char *sep2 = memchr(agent_name, '\0', token_len - (size_t)(agent_name - (char *)token_raw));
    if (!sep2) {
        fprintf(stderr, "error: malformed token\n");
        jsbridge_cleanup();
        return 1;
    }
    unsigned char *l2_key = (unsigned char *)(sep2 + 1);
    size_t l2_len = token_len - (size_t)(l2_key - token_raw);
    if (l2_len != 16) {
        fprintf(stderr, "error: invalid L2 key in token (%zu bytes, need 16)\n", l2_len);
        jsbridge_cleanup();
        return 1;
    }

    /* Build config from token */
    struct v84_config cfg;
    memset(&cfg, 0, sizeof(cfg));
    snprintf(cfg.vault_url, sizeof(cfg.vault_url), "https://%s:1984", vault_host);
    snprintf(cfg.agent_name, sizeof(cfg.agent_name), "%s", agent_name);
    memcpy(cfg.l2_key, l2_key, 16);

    /* L1 = first 8 bytes of L2 key, used as Bearer auth */
    char bearer[32];
    get_bearer(&cfg, bearer, sizeof(bearer));

    if (strcmp(command, "list") == 0) {
        const char *filter = NULL;
        for (int i = cmd_start; i < argc; i++) {
            if (argv[i][0] != '-') { filter = argv[i]; break; }
        }

        char url[1024];
        if (filter) {
            char encoded[512], path[768];
            url_encode(filter, encoded, sizeof(encoded));
            snprintf(path, sizeof(path), "/api/search?q=%s", encoded);
            build_url(url, sizeof(url), &cfg, path);
        } else {
            build_url(url, sizeof(url), &cfg, "/api/entries");
        }

        struct v84_response resp;
        if (http_get(url, bearer, cfg.agent_name, &resp) != 0) { fprintf(stderr, "error: request failed\n"); return 1; }
        if (resp.status != 200) { fprintf(stderr, "error: server returned %d\n", resp.status); free(resp.body); return 1; }
        if (json_output) { printf("%s\n", resp.body); free(resp.body); return 0; }

        cJSON *root = cJSON_Parse(resp.body); free(resp.body);
        if (!root) { fprintf(stderr, "error: invalid JSON\n"); return 1; }
        cJSON *entries = cJSON_IsArray(root) ? root : cJSON_GetObjectItem(root, "entries");
        if (!entries) entries = root;
        cJSON *entry;
        cJSON_ArrayForEach(entry, entries) {
            printf("%s\t%s\t%s\n",
                cJSON_GetStringValue(cJSON_GetObjectItem(entry, "entry_id")) ?: "?",
                cJSON_GetStringValue(cJSON_GetObjectItem(entry, "type")) ?: "?",
                cJSON_GetStringValue(cJSON_GetObjectItem(entry, "title")) ?: "?");
        }
        cJSON_Delete(root);
        return 0;
    }

    if (strcmp(command, "get") == 0 || strcmp(command, "totp") == 0) {
        int is_totp = (strcmp(command, "totp") == 0);
        const char *query = NULL;
        for (int i = cmd_start; i < argc; i++) {
            if (argv[i][0] != '-') { query = argv[i]; break; }
        }
        if (!query) { fprintf(stderr, "error: query required\nusage: vault1984-cli --token TOKEN %s <query>\n", command); return 1; }

        /* Search */
        char url[1024], encoded[512], path[768];
        url_encode(query, encoded, sizeof(encoded));
        snprintf(path, sizeof(path), "/api/search?q=%s", encoded);
        build_url(url, sizeof(url), &cfg, path);

        struct v84_response resp;
        if (http_get(url, bearer, cfg.agent_name, &resp) != 0) { fprintf(stderr, "error: search failed\n"); return 1; }
        if (resp.status != 200) { fprintf(stderr, "error: server returned %d\n", resp.status); free(resp.body); return 1; }

        cJSON *results = cJSON_Parse(resp.body); free(resp.body);
        if (!results) { fprintf(stderr, "error: invalid JSON\n"); return 1; }
        cJSON *arr = cJSON_IsArray(results) ? results : cJSON_GetObjectItem(results, "entries");
        if (!arr) arr = results;
        cJSON *first = cJSON_GetArrayItem(arr, 0);
        if (!first) { fprintf(stderr, "error: no results for '%s'\n", query); cJSON_Delete(results); return 1; }
        const char *entry_id = cJSON_GetStringValue(cJSON_GetObjectItem(first, "entry_id"));
        if (!entry_id) { fprintf(stderr, "error: missing entry_id\n"); cJSON_Delete(results); return 1; }

        if (is_totp) {
            snprintf(path, sizeof(path), "/api/ext/totp/%s", entry_id);
            build_url(url, sizeof(url), &cfg, path);
            cJSON_Delete(results);
            if (http_get(url, bearer, cfg.agent_name, &resp) != 0) { fprintf(stderr, "error: TOTP request failed\n"); return 1; }
            if (resp.status != 200) { fprintf(stderr, "error: server returned %d\n", resp.status); free(resp.body); return 1; }
            cJSON *totp = cJSON_Parse(resp.body); free(resp.body);
            if (!totp) { fprintf(stderr, "error: invalid JSON\n"); return 1; }
            const char *code = cJSON_GetStringValue(cJSON_GetObjectItem(totp, "code"));
            if (!code) { fprintf(stderr, "error: no TOTP code\n"); cJSON_Delete(totp); return 1; }
            printf("%s\n", code);
            cJSON_Delete(totp);
        } else {
            snprintf(path, sizeof(path), "/api/entries/%s", entry_id);
            build_url(url, sizeof(url), &cfg, path);
            cJSON_Delete(results);
            if (http_get(url, bearer, cfg.agent_name, &resp) != 0) { fprintf(stderr, "error: fetch failed\n"); return 1; }
            if (resp.status != 200) { fprintf(stderr, "error: server returned %d\n", resp.status); free(resp.body); return 1; }
            if (json_output) { printf("%s\n", resp.body); free(resp.body); return 0; }

            cJSON *entry = cJSON_Parse(resp.body); free(resp.body);
            if (!entry) { fprintf(stderr, "error: invalid JSON\n"); return 1; }
            cJSON *data = cJSON_GetObjectItem(entry, "data");
            if (!data) data = cJSON_GetObjectItem(entry, "vault_data");
            if (data) {
                cJSON *fields = cJSON_GetObjectItem(data, "fields");
                cJSON *field;
                cJSON_ArrayForEach(field, fields) {
                    const char *label = cJSON_GetStringValue(cJSON_GetObjectItem(field, "label"));
                    const char *value = cJSON_GetStringValue(cJSON_GetObjectItem(field, "value"));
                    cJSON *tier_j = cJSON_GetObjectItem(field, "tier");
                    cJSON *l2_j = cJSON_GetObjectItem(field, "l2");
                    int tier_val = tier_j ? tier_j->valueint : (l2_j && cJSON_IsTrue(l2_j) ? 3 : 1);
                    if (!label) continue;

                    if (tier_val >= 3) {
                        printf("%s: [L3 -- requires hardware key]\n", label);
                    } else if (tier_val == 2 && value && value[0]) {
                        /* Decrypt L2 field locally */
                        char *pt = jsbridge_decrypt_field(cfg.l2_key, 16, label, value);
                        if (pt) {
                            printf("%s: %s\n", label, pt);
                            free(pt);
                        } else {
                            printf("%s: [L2 -- decryption failed]\n", label);
                        }
                    } else {
                        printf("%s: %s\n", label, value ? value : "");
                    }
                }
            }
            cJSON_Delete(entry);
        }
        return 0;
    }

    fprintf(stderr, "error: unknown command '%s'\n", command);
    usage();
    return 1;
}
