/**
 * Asymmetric crypto helpers built on Node's builtin `crypto` (no native deps).
 *
 *  - Ed25519 raw-key <-> KeyObject bridging for CSR proof-of-possession (T8/T15 INV14) and
 *    the bind-time CA leaf signature.
 *  - X25519 seal/open (ECIES-style) for wrapping the per-host content secret to the enrolled
 *    identity (T8 FIX 3 INV4/INV5).
 *
 * NOTE (integration seam): production uses real PKCS#10 CSRs + X.509 leaves (`@peculiar/x509`)
 * and converts the Ed25519 enrollment key to X25519 (ed2curve). Here the CSR is modelled as an
 * Ed25519 proof-of-possession signature and the wrap targets a raw X25519 public key, which
 * exercises the SAME security properties (possession proof, per-host wrap, no raw secret at rest)
 * with builtin crypto only. Swapping in the X.509 stack does not change any §4 contract shape.
 */
import {
  createPublicKey,
  createPrivateKey,
  sign as edSign,
  verify as edVerify,
  generateKeyPairSync,
  diffieHellman,
  hkdfSync,
  randomBytes,
  createCipheriv,
  createDecipheriv,
  type KeyObject,
} from 'node:crypto'

const ED25519_SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex')
const X25519_SPKI_PREFIX = Buffer.from('302a300506032b656e032100', 'hex')

/** Wrap a raw 32-byte Ed25519 public key into a verifiable KeyObject. */
export function ed25519PublicKeyFromRaw(raw: Uint8Array): KeyObject {
  if (raw.length !== 32) throw new Error('ed25519 public key must be 32 bytes')
  return createPublicKey({
    key: Buffer.concat([ED25519_SPKI_PREFIX, Buffer.from(raw)]),
    format: 'der',
    type: 'spki',
  })
}

/** Verify an Ed25519 signature over `message` by the raw public key. Never throws. */
export function ed25519Verify(rawPub: Uint8Array, message: Uint8Array, sig: Uint8Array): boolean {
  try {
    const key = ed25519PublicKeyFromRaw(rawPub)
    return edVerify(null, Buffer.from(message), key, Buffer.from(sig))
  } catch {
    return false
  }
}

/** Test/util: generate an Ed25519 keypair, returning raw pubkey + a KeyObject private key. */
export function generateEd25519(): { publicKeyRaw: Uint8Array; privateKey: KeyObject } {
  const { publicKey, privateKey } = generateKeyPairSync('ed25519')
  const spki = publicKey.export({ format: 'der', type: 'spki' }) as Buffer
  const raw = spki.subarray(spki.length - 32)
  return { publicKeyRaw: new Uint8Array(raw), privateKey }
}

/** Test/util: sign a message with an Ed25519 private KeyObject. */
export function ed25519Sign(privateKey: KeyObject, message: Uint8Array): Uint8Array {
  return new Uint8Array(edSign(null, Buffer.from(message), privateKey))
}

// ---- X25519 wrap/unwrap (host content secret, FIX 3) --------------------------------------

export function generateX25519(): { publicKeyRaw: Uint8Array; privateKey: KeyObject } {
  const { publicKey, privateKey } = generateKeyPairSync('x25519')
  const spki = publicKey.export({ format: 'der', type: 'spki' }) as Buffer
  const raw = spki.subarray(spki.length - 32)
  return { publicKeyRaw: new Uint8Array(raw), privateKey }
}

function x25519PublicKeyFromRaw(raw: Uint8Array): KeyObject {
  if (raw.length !== 32) throw new Error('x25519 public key must be 32 bytes')
  return createPublicKey({
    key: Buffer.concat([X25519_SPKI_PREFIX, Buffer.from(raw)]),
    format: 'der',
    type: 'spki',
  })
}

const WRAP_INFO = Buffer.from('relay-cp/host-content-secret/v1')

/**
 * Seal `secret` to a recipient X25519 public key (ephemeral-static ECDH → HKDF → AES-256-GCM).
 * Output layout: ephPub(32) || iv(12) || tag(16) || ciphertext. The raw secret never appears in
 * the output. A fresh ephemeral key per call makes every wrap distinct (per-host revocable).
 */
export function sealToX25519(recipientRawPub: Uint8Array, secret: Uint8Array): Uint8Array {
  const recipient = x25519PublicKeyFromRaw(recipientRawPub)
  const eph = generateKeyPairSync('x25519')
  const ephSpki = eph.publicKey.export({ format: 'der', type: 'spki' }) as Buffer
  const ephPubRaw = ephSpki.subarray(ephSpki.length - 32)
  const shared = diffieHellman({ privateKey: eph.privateKey, publicKey: recipient })
  const aeadKey = Buffer.from(hkdfSync('sha256', shared, ephPubRaw, WRAP_INFO, 32))
  const iv = randomBytes(12)
  const cipher = createCipheriv('aes-256-gcm', aeadKey, iv)
  const ct = Buffer.concat([cipher.update(Buffer.from(secret)), cipher.final()])
  const tag = cipher.getAuthTag()
  return new Uint8Array(Buffer.concat([ephPubRaw, iv, tag, ct]))
}

/** Open a sealed blob with the recipient's X25519 private key. Throws on tamper/wrong key. */
export function openFromX25519(recipientPriv: KeyObject, blob: Uint8Array): Uint8Array {
  const buf = Buffer.from(blob)
  const ephPubRaw = buf.subarray(0, 32)
  const iv = buf.subarray(32, 44)
  const tag = buf.subarray(44, 60)
  const ct = buf.subarray(60)
  const ephPub = x25519PublicKeyFromRaw(new Uint8Array(ephPubRaw))
  const shared = diffieHellman({ privateKey: recipientPriv, publicKey: ephPub })
  const aeadKey = Buffer.from(hkdfSync('sha256', shared, ephPubRaw, WRAP_INFO, 32))
  const decipher = createDecipheriv('aes-256-gcm', aeadKey, iv)
  decipher.setAuthTag(tag)
  return new Uint8Array(Buffer.concat([decipher.update(ct), decipher.final()]))
}

export { createPrivateKey, type KeyObject }
