#!/usr/bin/env node
/**
 * n_signer_qube_example_qrexec.js — connect to n_signer in the nostr_signer
 * qube via Qubes qrexec (qubes.NsignerRpc service) and call get_public_key
 * for nostr_index 0 and 1, printing both hex pubkey and bech32 npub.
 *
 * This uses Qubes OS's built-in secure IPC (qrexec) instead of FIPS TCP.
 * No network connectivity is required — all traffic stays within the host.
 *
 * Prerequisites:
 *   - The qubes.NsignerRpc service must be installed in the nostr_signer qube
 *     (see packaging/qubes/install-service.sh)
 *   - The qrexec policy must be installed in dom0
 *     (see packaging/qubes/install-policy.sh)
 *   - The nostr_signer qube must be tagged with 'nsigner-signer'
 *     (qvm-tags nostr_signer add nsigner-signer)
 *   - A mnemonic file must exist at /home/user/.nsigner_mnemonic in the
 *     nostr_signer qube
 *
 * Usage:
 *   node n_signer_qube_example_qrexec.js [target_qube]
 *   node n_signer_qube_example_qrexec.js nostr_signer
 *
 * If no argument is given, defaults to "nostr_signer".
 *
 * Protocol:
 *   - qrexec-client-vm spawns the qubes.NsignerRpc service in the target qube
 *   - The service runs: nsigner --listen qrexec --allow-all --mnemonic-fd 3
 *   - We send one framed JSON-RPC request via stdin, receive one framed response via stdout
 *   - Framing: 4-byte big-endian length prefix + JSON payload
 *   - Auth: not required in qrexec mode (caller identity is qubes:<source-vm>)
 */

const { spawn } = require("child_process");
const { nip19 } = require("nostr-tools");

/**
 * Frame a JSON-RPC request: 4-byte big-endian length + JSON payload.
 */
function frameRequest(obj) {
  const payload = Buffer.from(JSON.stringify(obj), "utf8");
  const header = Buffer.alloc(4);
  header.writeUInt32BE(payload.length, 0);
  return Buffer.concat([header, payload]);
}

/**
 * Parse a framed response from the qrexec stdout buffer.
 */
function parseFramedResponse(buf) {
  if (buf.length < 4) {
    throw new Error("short response (missing frame header)");
  }
  const len = buf.readUInt32BE(0);
  const payload = buf.subarray(4, 4 + len);
  if (payload.length !== len) {
    throw new Error(`short response payload: expected ${len}, got ${payload.length}`);
  }
  return JSON.parse(payload.toString("utf8"));
}

/**
 * Call nsigner via qrexec. Sends one framed request, receives one framed response.
 * Each call spawns a fresh qrexec-client-vm process (one request per invocation).
 */
function callNsignerQrexec(targetQube, request) {
  return new Promise((resolve, reject) => {
    const framed = frameRequest(request);
    const proc = spawn("qrexec-client-vm", [targetQube, "qubes.NsignerRpc"], {
      stdio: ["pipe", "pipe", "pipe"],
    });

    const stdoutChunks = [];
    const stderrChunks = [];

    proc.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
    proc.stderr.on("data", (chunk) => stderrChunks.push(chunk));

    proc.on("error", (err) => {
      reject(new Error(`failed to spawn qrexec-client-vm: ${err.message}`));
    });

    proc.on("close", (code) => {
      if (code !== 0) {
        const stderr = Buffer.concat(stderrChunks).toString("utf8");
        reject(new Error(`qrexec-client-vm exited with code ${code}: ${stderr.trim()}`));
        return;
      }
      try {
        const response = parseFramedResponse(Buffer.concat(stdoutChunks));
        resolve(response);
      } catch (e) {
        reject(new Error(`failed to parse response: ${e.message}`));
      }
    });

    // Send the framed request and close stdin
    proc.stdin.write(framed);
    proc.stdin.end();
  });
}

/**
 * Query get_public_key for a given nostr_index via qrexec.
 * No auth envelope needed — qrexec mode uses qubes:<source-vm> as caller identity.
 */
async function getPublicKey(targetQube, nostrIndex) {
  const request = {
    id: String(nostrIndex),
    method: "get_public_key",
    params: [{ nostr_index: nostrIndex }],
  };

  return callNsignerQrexec(targetQube, request);
}

async function main() {
  const targetQube = process.argv[2] || "nostr_signer";

  console.log(`Calling n_signer in qube "${targetQube}" via qrexec...`);
  console.log("Querying get_public_key for nostr_index 0 and 1...\n");

  let failures = 0;

  for (const index of [0, 1]) {
    try {
      const response = await getPublicKey(targetQube, index);

      if (response.error) {
        console.log(`index ${index}: ERROR: ${JSON.stringify(response.error)}`);
        failures++;
        continue;
      }

      const pubkeyHex = response.result;
      const npub = nip19.npubEncode(pubkeyHex);
      console.log(`index ${index}: hex=${pubkeyHex}  npub=${npub}`);
    } catch (e) {
      console.log(`index ${index}: FAILED - ${e.message}`);
      failures++;
    }
  }

  if (failures > 0) {
    console.log(`\n${failures} query(s) failed`);
    process.exit(1);
  }

  console.log("\nAll queries succeeded");
}

main().catch((e) => {
  console.error(e);
  process.exit(1);
});
