/**
 * CSR proof-of-possession (INV14). Models a PKCS#10 CSR as `embeddedPub(32) || sig(64)` where
 * `sig = Ed25519(privateKey, CSR_CHALLENGE || embeddedPub)`. Verifying the signature against the
 * embedded pubkey proves the requester holds the matching private key (the exact property a real
 * PKCS#10 self-signature provides) — using builtin crypto only.
 *
 * INTEGRATION SEAM: production parses a real PKCS#10 DER (`@peculiar/x509`) and checks the
 * self-signature. Swapping it in does not change `signHostLeaf`'s check ORDER or reject path.
 */
import { ed25519Sign, ed25519Verify, type KeyObject } from '../util/crypto.js'

const CSR_CHALLENGE = new TextEncoder().encode('relay-cp/csr-pop/v1|')

/** Test/agent helper: build a CSR proving possession of `privateKey` for `embeddedPub`. */
export function buildCsr(privateKey: KeyObject, embeddedPub: Uint8Array): Uint8Array {
  const message = concat(CSR_CHALLENGE, embeddedPub)
  const sig = ed25519Sign(privateKey, message)
  return concat(embeddedPub, sig)
}

export interface CsrParts {
  readonly embeddedPub: Uint8Array
  readonly sig: Uint8Array
}

/** Parse the modelled CSR bytes. Throws on wrong length. */
export function parseCsr(csr: Uint8Array): CsrParts {
  if (csr.length !== 96) throw new Error('malformed csr')
  return { embeddedPub: csr.subarray(0, 32), sig: csr.subarray(32, 96) }
}

/**
 * Verify CSR proof-of-possession: the embedded pubkey's private key signed the challenge.
 * Independent of any registry state (a forged signature is refused even for a registered key).
 */
export function verifyCsrPoP(csr: Uint8Array): { ok: boolean; embeddedPub: Uint8Array } {
  let parts: CsrParts
  try {
    parts = parseCsr(csr)
  } catch {
    return { ok: false, embeddedPub: new Uint8Array(0) }
  }
  const message = concat(CSR_CHALLENGE, parts.embeddedPub)
  const ok = ed25519Verify(parts.embeddedPub, message, parts.sig)
  return { ok, embeddedPub: parts.embeddedPub }
}

function concat(a: Uint8Array, b: Uint8Array): Uint8Array {
  const out = new Uint8Array(a.length + b.length)
  out.set(a, 0)
  out.set(b, a.length)
  return out
}
