#!/usr/bin/env node

/**
 * NIP-46 Client-Initiated Auth Script
 *
 * Generates an ephemeral client keypair and a `nostrconnect://` URI.
 * Import the URI into a remote signer app (e.g. Amber) to authorize
 * the client key. Once authorized, the script outputs:
 *
 *   - bunker:// URI (for ZAPSTORE_BUNKER_URL)
 *   - client secret key hex (for ZAPSTORE_CLIENT_KEY)
 *
 * It also writes the client key to ~/.config/zsp/bunker-keys/<bunkerPubkey>.key
 * so that `zsp` can use it immediately.
 *
 * Usage:
 *   node scripts/nip46-auth.mjs [--relay wss://relay.example.com] [--name MyApp] [--timeout 300]
 */

import { NPool, NRelay1, NConnectSigner, NSecSigner } from '@nostrify/nostrify';
import { generateSecretKey, getPublicKey, nip19 } from 'nostr-tools';
import { bytesToHex } from '@noble/hashes/utils';
import QRCode from 'qrcode';
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';

// ---------------------------------------------------------------------------
// CLI args
// ---------------------------------------------------------------------------

function parseArgs() {
  const args = process.argv.slice(2);
  const result = {
    relays: [],
    name: 'Agora',
    timeout: 300, // seconds
  };

  for (let i = 0; i < args.length; i++) {
    switch (args[i]) {
      case '--relay':
        result.relays.push(args[++i]);
        break;
      case '--name':
        result.name = args[++i];
        break;
      case '--timeout':
        result.timeout = parseInt(args[++i], 10);
        break;
      case '--help':
      case '-h':
        console.log(`Usage: node scripts/nip46-auth.mjs [options]

Options:
  --relay <url>    Relay URL for NIP-46 communication (repeatable)
                   Default: wss://relay.agora.spot
  --name <name>    Application name shown to the signer
                   Default: Agora
  --timeout <sec>  How long to wait for signer approval (seconds)
                   Default: 300 (5 minutes)
  --help, -h       Show this help message
`);
        process.exit(0);
    }
  }

  if (result.relays.length === 0) {
    result.relays.push('wss://relay.agora.spot');
  }

  return result;
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------

async function main() {
  const opts = parseArgs();

  // 1. Generate ephemeral client keypair
  const clientSecretKey = generateSecretKey();
  const clientPubkey = getPublicKey(clientSecretKey);
  const clientHex = bytesToHex(clientSecretKey);

  console.log('');
  console.log('=== NIP-46 Client-Initiated Auth ===');
  console.log('');
  console.log(`Client pubkey: ${clientPubkey}`);
  console.log(`Relay(s):      ${opts.relays.join(', ')}`);
  console.log(`Timeout:       ${opts.timeout}s`);
  console.log('');

  // 2. Generate random secret
  const randomBytes = new Uint8Array(4);
  crypto.getRandomValues(randomBytes);
  const secret = Array.from(randomBytes)
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('');

  // 3. Build nostrconnect:// URI
  const searchParams = new URLSearchParams();
  for (const relay of opts.relays) {
    searchParams.append('relay', relay);
  }
  searchParams.set('secret', secret);
  searchParams.set('name', opts.name);

  const nostrConnectURI = `nostrconnect://${clientPubkey}?${searchParams.toString()}`;

  console.log('Scan this QR code with your signer app (e.g. Amber):');
  console.log('');
  console.log(await QRCode.toString(nostrConnectURI, { type: 'terminal', small: true }));
  console.log('Or import this URI manually:');
  console.log('');
  console.log(`  ${nostrConnectURI}`);
  console.log('');
  console.log('Waiting for signer to approve the connection...');
  console.log('');

  // 4. Set up relay pool
  const pool = new NPool({
    open: (url) => new NRelay1(url),
    reqRouter: async (filters) => new Map(opts.relays.map((r) => [r, filters])),
    eventRouter: async () => opts.relays,
  });

  const clientSigner = new NSecSigner(clientSecretKey);
  const relayGroup = pool.group(opts.relays);

  // 5. Subscribe and wait for the signer's response
  const signal = AbortSignal.timeout(opts.timeout * 1000);

  const sub = relayGroup.req(
    [{ kinds: [24133], '#p': [clientPubkey], limit: 1 }],
    { signal },
  );

  let bunkerPubkey;
  let userPubkey;

  try {
    for await (const msg of sub) {
      if (msg[0] === 'CLOSED') {
        throw new Error('Relay closed the subscription before signer responded');
      }
      if (msg[0] === 'EVENT') {
        const event = msg[2];

        let decrypted;
        try {
          decrypted = await clientSigner.nip44.decrypt(event.pubkey, event.content);
        } catch {
          // Could not decrypt -- not for us, skip
          continue;
        }

        let response;
        try {
          response = JSON.parse(decrypted);
        } catch {
          continue;
        }

        if (response.result !== secret && response.result !== 'ack') {
          continue;
        }

        bunkerPubkey = event.pubkey;

        console.log(`Signer responded! Bunker pubkey: ${bunkerPubkey}`);
        console.log('');

        // 6. Get user pubkey via the now-established connection
        const signer = new NConnectSigner({
          relay: relayGroup,
          pubkey: bunkerPubkey,
          signer: clientSigner,
          timeout: 60_000,
        });

        console.log('Requesting user public key...');
        userPubkey = await signer.getPublicKey();
        console.log(`User pubkey:   ${userPubkey}`);
        console.log('');

        break;
      }
    }
  } catch (err) {
    if (err.name === 'TimeoutError' || err.name === 'AbortError') {
      console.error(`Timed out after ${opts.timeout}s waiting for signer approval.`);
      console.error('Make sure you imported the nostrconnect:// URI into your signer app.');
      process.exit(1);
    }
    throw err;
  }

  if (!bunkerPubkey || !userPubkey) {
    console.error('Failed to establish connection with remote signer.');
    process.exit(1);
  }

  // 7. Build bunker:// URI (for CI)
  const bunkerParams = new URLSearchParams();
  for (const relay of opts.relays) {
    bunkerParams.append('relay', relay);
  }
  const bunkerURI = `bunker://${bunkerPubkey}?${bunkerParams.toString()}`;

  // 8. Write client key to zsp config
  const zspDir = path.join(os.homedir(), '.config', 'zsp', 'bunker-keys');
  const zspKeyFile = path.join(zspDir, `${bunkerPubkey}.key`);

  fs.mkdirSync(zspDir, { recursive: true });
  fs.writeFileSync(zspKeyFile, clientHex + '\n', { mode: 0o600 });

  // 9. Print results
  console.log('=== Connection Established ===');
  console.log('');
  console.log('Bunker URI (ZAPSTORE_BUNKER_URL):');
  console.log(`  ${bunkerURI}`);
  console.log('');
  console.log('Client secret key hex (ZAPSTORE_CLIENT_KEY):');
  console.log(`  ${clientHex}`);
  console.log('');
  console.log(`User pubkey:   ${userPubkey}`);
  console.log(`User npub:     ${nip19.npubEncode(userPubkey)}`);
  console.log('');
  console.log(`zsp client key written to: ${zspKeyFile}`);
  console.log('');
  console.log('Next steps:');
  console.log('  1. Update ZAPSTORE_BUNKER_URL in GitLab CI/CD variables');
  console.log('  2. Update ZAPSTORE_CLIENT_KEY in GitLab CI/CD variables');
  console.log('');

  // Clean up
  pool.close();
  process.exit(0);
}

main().catch((err) => {
  console.error('Fatal error:', err.message);
  process.exit(1);
});
