/**
 * T8 — bind-time mTLS leaf signer, registry-gated (INV14 registry half). ORDER OF CHECKS (all
 * must pass, SAME reject path):
 *   1. CSR proof-of-possession — verify the CSR self-signature against its embedded pubkey.
 *   2. embedded pubkey == caller-supplied `agentPubkey` (no substitution).
 *   3. (hostId, agentPubkey) is an ACTIVE, non-revoked row in the host registry.
 * A failure at ANY step rejects identically; the KMS `sign()` is NEVER invoked when any check
 * fails. Signing itself is `CaSigner.sign()` (KMS, §3.1) — never a raw private key in memory.
 */
import type { HostStore } from '../store/ports.js'
import type { CaSigner } from '../boot/ca-wiring.js'
import { verifyCsrPoP } from './csr.js'
import { timingSafeEqualBytes, bytesToBase64 } from '../util/bytes.js'

export class LeafSignError extends Error {
  constructor(public readonly code: 'csr_rejected' | 'not_registered') {
    super('leaf signing refused') // uniform message — do not leak which check failed
  }
}

export interface LeafSignerDeps {
  readonly hosts: HostStore
  readonly signer: CaSigner
  readonly caChainDer: readonly Uint8Array[]
  /** Leaf validity in seconds (short-lived, INV14). Default 24h. */
  readonly leafTtlSec?: number
}

export interface LeafSigner {
  signHostLeaf(
    hostId: string,
    agentPubkey: Uint8Array,
    csr: Uint8Array,
  ): Promise<{ cert: Uint8Array; caChain: readonly Uint8Array[] }>
}

const DEFAULT_LEAF_TTL_SEC = 24 * 60 * 60

export function createLeafSigner(deps: LeafSignerDeps): LeafSigner {
  const ttl = deps.leafTtlSec ?? DEFAULT_LEAF_TTL_SEC
  return {
    async signHostLeaf(hostId, agentPubkey, csr) {
      // 1. proof-of-possession (independent of registry state)
      const pop = verifyCsrPoP(csr)
      if (!pop.ok) throw new LeafSignError('csr_rejected')
      // 2. no substitution: CSR pubkey must equal the presented agentPubkey
      if (!timingSafeEqualBytes(pop.embeddedPub, agentPubkey)) throw new LeafSignError('csr_rejected')
      // 3. registry gate: host bound, active, non-revoked, pubkey matches (INV14)
      const host = await deps.hosts.get(hostId)
      if (host === null || host.status === 'revoked') throw new LeafSignError('not_registered')
      if (!timingSafeEqualBytes(host.agentPubkey, agentPubkey)) throw new LeafSignError('not_registered')

      // Only now do we invoke KMS sign() over the to-be-signed leaf.
      const notAfter = Math.floor(Date.now() / 1000) + ttl
      const tbs = new TextEncoder().encode(
        JSON.stringify({
          v: 1,
          hostId,
          subjectSpki: bytesToBase64(agentPubkey), // subject pubkey == agentPubkey (assertable in tests)
          notAfter,
        }),
      )
      const sig = await deps.signer.sign(tbs)
      const cert = new TextEncoder().encode(
        JSON.stringify({ tbs: bytesToBase64(tbs), sig: bytesToBase64(sig) }),
      )
      return { cert, caChain: deps.caChainDer }
    },
  }
}
