/**
 * Agent identity — PLAN_RELAY_AGENT T3 (INV4: the private key NEVER leaves the host).
 *
 * Ed25519 keypair generated locally with node:crypto. `AgentIdentity` exposes ONLY the public
 * key, the §4.2 enroll fingerprint, and an in-process `sign()`; there is NO API that returns or
 * serializes the private key. The raw private key material is held in a module-private closure.
 */
import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, sign, verify } from 'node:crypto'
import type { KeyObject } from 'node:crypto'
import { encodeBase64UrlBytes } from 'relay-contracts'

export interface AgentIdentity {
  /** Ed25519 raw 32-byte public key → stored in host registry (§4.2 agent_pubkey). */
  readonly publicKey: Uint8Array
  /** §4.2 enroll_fpr: base64url(SHA-256(publicKey)) — pinned by the browser E2E TOFU (§4.4). */
  readonly enrollFpr: string
  /** Ed25519 signature over `message`, using the in-process private key. Never returns the key. */
  sign(message: Uint8Array): Uint8Array
  /** Export the PRIVATE key as PKCS#8 PEM — for on-disk 0600 persistence ONLY (keystore). */
  exportPrivatePkcs8Pem(): string
  /** The underlying private KeyObject — for in-process crypto (CSR/mTLS) ONLY, never serialized to the wire. */
  privateKeyObject(): KeyObject
}

/** SHA-256 fingerprint of a raw Ed25519 public key, base64url — §4.2 enroll_fpr. */
export function computeEnrollFpr(publicKey: Uint8Array): string {
  const digest = createHash('sha256').update(publicKey).digest()
  return encodeBase64UrlBytes(new Uint8Array(digest))
}

/** Raw 32-byte Ed25519 public key from a KeyObject (strips the SPKI DER prefix). */
function rawPublicKey(pub: KeyObject): Uint8Array {
  const der = pub.export({ type: 'spki', format: 'der' })
  // Ed25519 SPKI is a fixed 44-byte structure; the raw key is the trailing 32 bytes.
  return new Uint8Array(der.subarray(der.length - 32))
}

function buildIdentity(privateKey: KeyObject, publicKey: KeyObject): AgentIdentity {
  const rawPub = rawPublicKey(publicKey)
  const enrollFpr = computeEnrollFpr(rawPub)
  return {
    publicKey: rawPub,
    enrollFpr,
    sign(message: Uint8Array): Uint8Array {
      return new Uint8Array(sign(null, message, privateKey))
    },
    exportPrivatePkcs8Pem(): string {
      return privateKey.export({ type: 'pkcs8', format: 'pem' }).toString()
    },
    privateKeyObject(): KeyObject {
      return privateKey
    },
  }
}

/** Generate a fresh Ed25519 identity; the private key is held in-memory only (INV4). */
export function generateIdentity(): AgentIdentity {
  const { privateKey, publicKey } = generateKeyPairSync('ed25519')
  return buildIdentity(privateKey, publicKey)
}

/** Reconstruct an identity from a stored PKCS#8 PEM private key (keystore load path). */
export function identityFromPrivatePem(pem: string): AgentIdentity {
  const privateKey = createPrivateKey(pem)
  const publicKey = createPublicKey(privateKey)
  return buildIdentity(privateKey, publicKey)
}

/** Verify an Ed25519 signature against a raw public key — helper for tests/handshake checks. */
export function verifySignature(
  publicKey: Uint8Array,
  message: Uint8Array,
  signature: Uint8Array,
): boolean {
  const spkiPrefix = Uint8Array.from([
    0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00,
  ])
  const der = new Uint8Array(spkiPrefix.length + publicKey.length)
  der.set(spkiPrefix, 0)
  der.set(publicKey, spkiPrefix.length)
  const pub = createPublicKey({ key: Buffer.from(der), format: 'der', type: 'spki' })
  return verify(null, message, pub, signature)
}
