/**
 * PKCS#10 CSR over the Ed25519 identity — PLAN_RELAY_AGENT T4.
 *
 * Node has no built-in CSR generator, so this is a compact, self-contained DER encoder that
 * emits a standard PKCS#10 CertificationRequest signed with the in-process Ed25519 key (INV4 —
 * only the PUBLIC key + a signature leave; the private key is never serialized here).
 *
 * NOTE (cross-plan / open Q#1): the exact SPIFFE-style cert PROFILE (SAN = subdomain vs a SPIFFE
 * URI) is set by P3's CA. This builds the standard subject-CN PKCS#10 shape; when P3 freezes its
 * profile, extend `buildCsr`'s attributes here. That is the single integration point.
 */
import type { AgentIdentity } from '../keys/identity.js'

// --- minimal DER encoding helpers -------------------------------------------------------------

function derLen(len: number): Uint8Array {
  if (len < 0x80) return Uint8Array.from([len])
  const bytes: number[] = []
  let n = len
  while (n > 0) {
    bytes.unshift(n & 0xff)
    n >>= 8
  }
  return Uint8Array.from([0x80 | bytes.length, ...bytes])
}

function tlv(tag: number, value: Uint8Array): Uint8Array {
  const len = derLen(value.length)
  const out = new Uint8Array(1 + len.length + value.length)
  out[0] = tag
  out.set(len, 1)
  out.set(value, 1 + len.length)
  return out
}

function concat(chunks: readonly Uint8Array[]): Uint8Array {
  const total = chunks.reduce((s, c) => s + c.length, 0)
  const out = new Uint8Array(total)
  let off = 0
  for (const c of chunks) {
    out.set(c, off)
    off += c.length
  }
  return out
}

const SEQUENCE = 0x30
const SET = 0x31
const INTEGER = 0x02
const BIT_STRING = 0x03
const OID = 0x06
const UTF8_STRING = 0x0c
const CONTEXT_0 = 0xa0

// OID 2.5.4.3 (commonName), 1.3.101.112 (Ed25519), 1.2.840.10045.4.3.2 (ecdsa-with-SHA256) as
// pre-encoded DER value bytes.
const OID_CN = Uint8Array.from([0x55, 0x04, 0x03])
const OID_ED25519 = Uint8Array.from([0x2b, 0x65, 0x70])
const OID_ECDSA_WITH_SHA256 = Uint8Array.from([0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02])

/** SubjectPublicKeyInfo DER for a raw Ed25519 public key (fixed 44-byte structure). */
function spkiFromRawEd25519(raw: Uint8Array): Uint8Array {
  const algId = tlv(SEQUENCE, tlv(OID, OID_ED25519))
  const pubBits = tlv(BIT_STRING, concat([Uint8Array.from([0x00]), raw]))
  return tlv(SEQUENCE, concat([algId, pubBits]))
}

/**
 * SubjectPublicKeyInfo bytes for the CSR. For P-256, `id.publicKey` IS already the full EC SPKI DER
 * (built by `keys/identity.ts`), so it is embedded verbatim; for Ed25519 the raw 32-byte key is
 * wrapped into the fixed SPKI structure.
 */
function spkiFor(id: AgentIdentity): Uint8Array {
  return id.alg === 'p256' ? id.publicKey : spkiFromRawEd25519(id.publicKey)
}

/**
 * The signatureAlgorithm AlgorithmIdentifier: `SEQUENCE { OID }` (no parameters — RFC 5758 §3.2 for
 * ecdsa-with-SHA256, and Ed25519 likewise omits parameters).
 */
function sigAlgFor(id: AgentIdentity): Uint8Array {
  const oid = id.alg === 'p256' ? OID_ECDSA_WITH_SHA256 : OID_ED25519
  return tlv(SEQUENCE, tlv(OID, oid))
}

/** X.501 Name with a single CN=<subject> RDN. */
function nameFromCn(cn: string): Uint8Array {
  const atv = tlv(SEQUENCE, concat([tlv(OID, OID_CN), tlv(UTF8_STRING, new TextEncoder().encode(cn))]))
  const rdn = tlv(SET, atv)
  return tlv(SEQUENCE, rdn)
}

function toPem(der: Uint8Array, label: string): string {
  const b64 = Buffer.from(der).toString('base64')
  const lines = b64.match(/.{1,64}/g) ?? []
  return `-----BEGIN ${label}-----\n${lines.join('\n')}\n-----END ${label}-----\n`
}

/**
 * Build a PKCS#10 CSR (PEM) for `id` with subject CN=`subject`, signed by the identity's key. The
 * Ed25519 and P-256 (ecdsa-with-SHA256, FIX H-host-2) paths share this one encoder — only the SPKI
 * and the signatureAlgorithm differ. The private key is used in-process only; never serialized into
 * the output (INV4).
 */
export function buildCsr(id: AgentIdentity, subject: string): string {
  const version = tlv(INTEGER, Uint8Array.from([0x00]))
  const name = nameFromCn(subject)
  const spki = spkiFor(id)
  const attributes = tlv(CONTEXT_0, new Uint8Array(0)) // [0] IMPLICIT empty SET OF Attribute
  const requestInfo = tlv(SEQUENCE, concat([version, name, spki, attributes]))

  const signature = id.sign(requestInfo) // over CertificationRequestInfo, per id.alg
  const sigAlg = sigAlgFor(id)
  const sigBits = tlv(BIT_STRING, concat([Uint8Array.from([0x00]), signature]))

  const csr = tlv(SEQUENCE, concat([requestInfo, sigAlg, sigBits]))
  return toPem(csr, 'CERTIFICATE REQUEST')
}
