/* engram at-rest encryption — prototype sketch
 *
 * NOT WIRED UP. This file is intentionally not in the build. It is a sketch
 * that pairs with `at-rest-encryption.md` so the function signatures and the
 * field layout can be reviewed before the runtime PQ primitives stabilize.
 *
 * Once `el_runtime.c` has stable `el_sha3_256_*`, `el_hkdf_sha3_256`,
 * `pq_kem_encaps`, `pq_kem_decaps`, and `el_aes256_gcm_encrypt/decrypt`,
 * the seal/open path below should be lifted into el_runtime.c near the
 * existing `engram_save` / `engram_load` block (~line 3445), gated behind
 * the ENGRAM_SEAL_MODE env var.
 *
 * Dependencies (provided elsewhere — see at-rest-encryption.md §8):
 *   void el_hkdf_sha3_256(const uint8_t* ikm, size_t ikm_len,
 *                         const uint8_t* salt, size_t salt_len,
 *                         const uint8_t* info, size_t info_len,
 *                         uint8_t* okm, size_t okm_len);
 *
 *   int  el_aes256_gcm_encrypt(const uint8_t key[32],
 *                              const uint8_t nonce[12],
 *                              const uint8_t* aad, size_t aad_len,
 *                              const uint8_t* pt,  size_t pt_len,
 *                              uint8_t* ct,        // pt_len bytes
 *                              uint8_t tag[16]);
 *
 *   int  el_aes256_gcm_decrypt(const uint8_t key[32],
 *                              const uint8_t nonce[12],
 *                              const uint8_t* aad, size_t aad_len,
 *                              const uint8_t* ct,  size_t ct_len,
 *                              const uint8_t tag[16],
 *                              uint8_t* pt);        // ct_len bytes
 *
 *   int  pq_kem_encaps(const uint8_t* pk, size_t pk_len,
 *                      uint8_t* kem_ct, size_t* kem_ct_len,
 *                      uint8_t  shared[32]);
 *   int  pq_kem_decaps(const uint8_t* sk, size_t sk_len,
 *                      const uint8_t* kem_ct, size_t kem_ct_len,
 *                      uint8_t shared[32]);
 *
 *   void el_random_bytes(uint8_t* out, size_t n);
 *   void el_secure_zero(void* p, size_t n);
 */

#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

/* ── module state (mlocked) ───────────────────────────────────────────────── */

static uint8_t  g_engram_dek[32];
static int      g_engram_dek_set = 0;
static uint32_t g_engram_dek_epoch = 0;

/* Seal/unseal mode — read once at boot from ENGRAM_SEAL_MODE.
 * 0=off, 1=fields, 2=full. */
static int g_engram_seal_mode = 0;

/* ── per-record seal ──────────────────────────────────────────────────────── */

/* Wire format (before base64):
 *   byte  0       version=0x01
 *   bytes 1..4    epoch (big-endian u32)
 *   bytes 5..16   nonce (12B)
 *   bytes 17..    ciphertext (pt_len bytes)
 *   last 16B     GCM tag
 */
static char* eg_b64_encode(const uint8_t* data, size_t n);  /* declared elsewhere */

char* engram_aead_seal_field(const char* record_id, const char* plaintext)
{
    if (!g_engram_dek_set) return NULL;
    if (!record_id || !plaintext) return NULL;

    size_t pt_len = strlen(plaintext);
    uint32_t epoch = g_engram_dek_epoch;

    /* derive sub-key */
    uint8_t sub_key[32];
    uint8_t info[96];
    int info_len = snprintf((char*)info, sizeof(info), "%s:%u", record_id, epoch);
    if (info_len < 0 || (size_t)info_len >= sizeof(info)) return NULL;

    el_hkdf_sha3_256(g_engram_dek, 32,
                     (const uint8_t*)"engram-record-v1", 16,
                     info, (size_t)info_len,
                     sub_key, 32);

    /* random nonce */
    uint8_t nonce[12];
    el_random_bytes(nonce, 12);

    /* allocate output: header(17) + ct(pt_len) + tag(16) */
    size_t blob_len = 1 + 4 + 12 + pt_len + 16;
    uint8_t* blob = (uint8_t*)malloc(blob_len);
    if (!blob) { el_secure_zero(sub_key, 32); return NULL; }

    blob[0] = 0x01;
    blob[1] = (uint8_t)((epoch >> 24) & 0xff);
    blob[2] = (uint8_t)((epoch >> 16) & 0xff);
    blob[3] = (uint8_t)((epoch >>  8) & 0xff);
    blob[4] = (uint8_t)( epoch        & 0xff);
    memcpy(blob + 5, nonce, 12);

    int rc = el_aes256_gcm_encrypt(
        sub_key, nonce,
        (const uint8_t*)record_id, strlen(record_id),
        (const uint8_t*)plaintext, pt_len,
        blob + 1 + 4 + 12,                 /* ct */
        blob + 1 + 4 + 12 + pt_len);       /* tag */

    el_secure_zero(sub_key, 32);

    if (rc != 0) { free(blob); return NULL; }

    char* b64 = eg_b64_encode(blob, blob_len);
    free(blob);
    return b64;  /* caller prefixes "v1:<epoch>:" if desired */
}

/* Returns malloc'd plaintext (NUL-terminated) or NULL on AEAD failure / version
 * mismatch / DEK-epoch mismatch. */
char* engram_aead_open_field(const char* record_id, const char* b64_blob)
{
    if (!g_engram_dek_set || !record_id || !b64_blob) return NULL;

    size_t blob_len = 0;
    uint8_t* blob = NULL;
    extern uint8_t* eg_b64_decode(const char* s, size_t* out_len);
    blob = eg_b64_decode(b64_blob, &blob_len);
    if (!blob || blob_len < 1 + 4 + 12 + 16) { free(blob); return NULL; }

    if (blob[0] != 0x01) { free(blob); return NULL; }
    uint32_t epoch = ((uint32_t)blob[1] << 24)
                   | ((uint32_t)blob[2] << 16)
                   | ((uint32_t)blob[3] <<  8)
                   |  (uint32_t)blob[4];

    /* For the common case the on-disk epoch matches g_engram_dek_epoch.
     * During DEK rotation we may need a previous-epoch DEK in the keyring;
     * left as a TODO — this stub only handles the current epoch. */
    if (epoch != g_engram_dek_epoch) {
        free(blob);
        return NULL;
    }

    const uint8_t* nonce = blob + 5;
    size_t ct_len = blob_len - (1 + 4 + 12 + 16);
    const uint8_t* ct  = blob + 1 + 4 + 12;
    const uint8_t* tag = blob + 1 + 4 + 12 + ct_len;

    uint8_t sub_key[32];
    uint8_t info[96];
    int info_len = snprintf((char*)info, sizeof(info), "%s:%u", record_id, epoch);
    if (info_len < 0 || (size_t)info_len >= sizeof(info)) { free(blob); return NULL; }
    el_hkdf_sha3_256(g_engram_dek, 32,
                     (const uint8_t*)"engram-record-v1", 16,
                     info, (size_t)info_len,
                     sub_key, 32);

    char* pt = (char*)malloc(ct_len + 1);
    if (!pt) { el_secure_zero(sub_key, 32); free(blob); return NULL; }

    int rc = el_aes256_gcm_decrypt(
        sub_key, nonce,
        (const uint8_t*)record_id, strlen(record_id),
        ct, ct_len, tag,
        (uint8_t*)pt);

    el_secure_zero(sub_key, 32);
    free(blob);

    if (rc != 0) { free(pt); return NULL; }
    pt[ct_len] = '\0';
    return pt;
}

/* ── KEK boot ─────────────────────────────────────────────────────────────── */

/* Reads engram.kek.enc, runs Kyber decaps with the principal SK provided as
 * a buffer, derives the wrap key, AEAD-opens the sealed DEK, and installs it
 * into g_engram_dek.  Returns 0 on success, non-zero on failure. */
int engram_kek_unwrap(const char* kek_path,
                      const uint8_t* principal_sk, size_t sk_len)
{
    /* TODO: file format
     *   magic "ENGKEK01" (8B)
     *   version (1B)
     *   principal_id_len (2B BE) | principal_id
     *   kem_ct_len (4B BE) | kem_ct
     *   nonce (12B)
     *   sealed_dek_len (4B BE) | sealed_dek
     *   tag (16B)
     */
    (void)kek_path; (void)principal_sk; (void)sk_len;
    return -1;  /* TODO: parse file, pq_kem_decaps, HKDF, AEAD-open, install */
}

/* ── KEK init (first-time write) ──────────────────────────────────────────── */

int engram_kek_init(const char* kek_path,
                    const uint8_t* principal_pk, size_t pk_len)
{
    /* TODO:
     *   1. el_random_bytes(g_engram_dek, 32); g_engram_dek_epoch = 1;
     *   2. pq_kem_encaps(principal_pk) → (kem_ct, shared)
     *   3. wrap_key = HKDF-SHA3-256(shared, salt="engram-kek-v1",
     *                               info="dek-wrap", L=32)
     *   4. AEAD-seal DEK under wrap_key
     *   5. write file atomically (tmp + rename + fsync)
     *   6. el_secure_zero(shared, wrap_key)
     */
    (void)kek_path; (void)principal_pk; (void)pk_len;
    return -1;
}

/* ── DEK rotation ─────────────────────────────────────────────────────────── */

int engram_dek_rotate(void)
{
    /* TODO:
     *   - Allocate DEK_{n+1}.
     *   - Walk EngramStore: for every node and edge, re-seal each encrypted
     *     field under DEK_{n+1} with the new epoch.
     *   - Re-wrap DEK_{n+1} under current Principal pk → engram.kek.enc.new
     *   - Write a new snapshot via engram_save() (caller responsibility).
     *   - Atomic rename engram.kek.enc.new → engram.kek.enc.
     *   - el_secure_zero on DEK_n; bump g_engram_dek_epoch.
     */
    return -1;
}

/* ── Recovery (Shamir K-of-N) ─────────────────────────────────────────────── */

/* The recovery secret R is independent of the DEK.  R is split via Shamir;
 * each share is then PQ-wrapped to its shareholder via Kyber768.  R is the
 * AEAD wrap-key for an envelope sealing the DEK.
 *
 * Two on-disk artifacts:
 *   engram.recovery.shares     — public; one Kyber-wrapped Shamir share / shareholder
 *   engram.recovery.envelope   — AEAD(R, nonce, plaintext=DEK, ad="engram-recovery-v1")
 */

int engram_recovery_split(int threshold, int total,
                          const uint8_t** shareholder_pks,
                          const size_t*   shareholder_pk_lens,
                          const char**    shareholder_ids,
                          const char* shares_out_path,
                          const char* envelope_out_path)
{
    /* TODO:
     *   1. el_random_bytes(R, 32).
     *   2. Shamir-split R over GF(2^8) → total shares of {1B index, 32B y_i}.
     *   3. For each shareholder i: pq_kem_encaps(pk_i) → (kem_ct, shared);
     *      AEAD-seal share_i under HKDF(shared) → wrapped_share_i; write entry.
     *   4. AEAD-seal current DEK under R → envelope.
     *   5. Atomic-write both files; fsync.
     *   6. Zero R, all shared secrets.
     */
    (void)threshold; (void)total;
    (void)shareholder_pks; (void)shareholder_pk_lens; (void)shareholder_ids;
    (void)shares_out_path; (void)envelope_out_path;
    return -1;
}

int engram_recovery_reconstitute(const uint8_t* k_unwrapped_shares,
                                 size_t share_count,
                                 const char* envelope_path,
                                 uint8_t out_dek[32])
{
    /* TODO:
     *   1. Lagrange-interpolate to recover R from the K shares.
     *   2. AEAD-open the envelope under HKDF(R) → DEK.
     *   3. Zero R.
     */
    (void)k_unwrapped_shares; (void)share_count;
    (void)envelope_path; (void)out_dek;
    return -1;
}

/* ── Snapshot integration sketch ──────────────────────────────────────────── */

/*
 * engram_emit_node_json (around line 3409 in el_runtime.c) becomes:
 *
 *   if (g_engram_seal_mode == 1) {
 *       char* sealed = engram_aead_seal_field(n->id, n->content);
 *       jb_puts(b, ",\"content\":");  jb_emit_escaped(b, sealed ? sealed : "");
 *       free(sealed);
 *       // same for label, tags, metadata
 *   } else {
 *       jb_puts(b, ",\"content\":");  jb_emit_escaped(b, n->content);
 *   }
 *
 * engram_load (around line 3499) becomes the inverse: when reading each field,
 * if the daemon is in `fields` mode and the value is non-empty, run
 * engram_aead_open_field; on failure log and substitute "<corrupted>".
 *
 * `engram_save` must additionally write engram.kek.enc the first time (via
 * engram_kek_init) when ENGRAM_SEAL_MODE != off and no kek file exists.
 */
