/**
 * A6 (FIX H-host-3) — POST /renew (host) + POST /device/:id/renew (device) route tests (fastify
 * inject). Both routes are authenticated ONLY by the CURRENT client cert the mTLS terminator forwards
 * (injected here via the `x-client-cert` header); the body carries ONLY the new CSR. Proves: a valid
 * current cert + a valid SAME-KEY CSR → 201 with a real X.509 leaf carrying the SAME subdomain (no
 * smuggling); a missing / malformed current cert → 401; a revoked / unknown / mismatched identity →
 * 403; a DIFFERENT-key CSR → 400 (MVP same-key); over the per-identity rate → 429.
 */
import 'reflect-metadata'
import { describe, test, expect } from 'vitest'
import Fastify, { type FastifyInstance } from 'fastify'
import * as x509 from '@peculiar/x509'
import { webcrypto, generateKeyPairSync, randomUUID } from 'node:crypto'
import { createMemoryStores } from '../src/store/memory.js'
import { createHostRegistry, type HostRegistry } from '../src/registry/hosts.js'
import { createDeviceRegistry, createMemoryDeviceStore, type DeviceRegistry } from '../src/registry/devices.js'
import { fingerprint } from '../src/ca/fingerprint.js'
import { assembleCertificate } from '../src/ca/x509-assembler.js'
import { buildCsrEc } from '../src/ca/csr-ec.js'
import { createFrpClientLeafSigner } from '../src/ca/frpclient-issue.js'
import { createDeviceLeafSigner, type DeviceLeafSigner } from '../src/ca/device-issue.js'
import { createLeafRenewer } from '../src/ca/rotate.js'
import type { LeafSigner } from '../src/ca/sign.js'
import { inProcessP256CaSigner, type CaSigner } from '../src/boot/ca-wiring.js'
import { buildRenewRouter, createRenewRateLimiter, type RenewDeps } from '../src/api/renew.js'

x509.cryptoProvider.set(webcrypto)

const DAY_MS = 24 * 60 * 60 * 1000
const TRUST_DOMAIN = 'terminal.yaojia.wang'
const ZONE = 'terminal.yaojia.wang'

interface P256Ca {
  readonly caSigner: CaSigner
  readonly caCert: x509.X509Certificate
  readonly caDer: Uint8Array
}

async function makeP256Ca(cn: string): Promise<P256Ca> {
  const caKeys = generateKeyPairSync('ec', { namedCurve: 'P-256' })
  const caSpki = new Uint8Array(caKeys.publicKey.export({ format: 'der', type: 'spki' }))
  const caSigner = inProcessP256CaSigner(caKeys.privateKey)
  const now = Date.now()
  const caDer = await assembleCertificate({
    subjectPublicKey: caSpki,
    subject: `CN=${cn}`,
    issuer: `CN=${cn}`,
    serialNumber: Uint8Array.from([0x01]),
    notBefore: new Date(now - DAY_MS),
    notAfter: new Date(now + 365 * DAY_MS),
    extensions: [
      new x509.BasicConstraintsExtension(true, undefined, true),
      new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign, true),
    ],
    signer: caSigner,
    sigAlg: 'ecdsa-p256',
  })
  return { caSigner, caCert: new x509.X509Certificate(caDer), caDer }
}

async function spkiOf(pub: CryptoKey): Promise<Uint8Array> {
  return new Uint8Array(await webcrypto.subtle.exportKey('spki', pub))
}

/** base64-DER cert value for the `x-client-cert` header (what the mTLS terminator forwards). */
function certHeader(der: Uint8Array): string {
  return Buffer.from(der).toString('base64')
}

function b64Csr(der: Uint8Array): string {
  return Buffer.from(der).toString('base64')
}

function appWith(deps: RenewDeps): FastifyInstance {
  const app = Fastify({ logger: false })
  void app.register(buildRenewRouter(deps))
  return app
}

// ── HOST /renew ──────────────────────────────────────────────────────────────────────────────────
interface HostCtx {
  hosts: HostRegistry
  renewer: ReturnType<typeof createLeafRenewer>
  hostSigner: LeafSigner
  ca: P256Ca
  subdomain: string
  spki: Uint8Array
  keys: { readonly publicKey: CryptoKey; readonly privateKey: CryptoKey }
  csr: Uint8Array
  currentCertDer: Uint8Array
  accountId: string
  deviceSigner: DeviceLeafSigner
}

async function hostCtx(subdomain = 'alice', hosts?: HostRegistry): Promise<HostCtx> {
  const ca = await makeP256Ca('frp-client-CA')
  const stores = createMemoryStores()
  const reg = hosts ?? createHostRegistry({ hosts: stores.hosts })
  const { der: csr, keys } = await buildCsrEc(`CN=${subdomain}`)
  const spki = await spkiOf(keys.publicKey)
  const accountId = randomUUID()
  const host = await reg.bindHost({ accountId, subdomain, agentPubkey: spki, enrollFpr: fingerprint(spki) })
  const hostSigner = createFrpClientLeafSigner({
    hosts: stores.hosts,
    signer: ca.caSigner,
    issuerName: ca.caCert.subjectName,
    caChainDer: [ca.caDer],
    trustDomain: TRUST_DOMAIN,
    dnsZone: ZONE,
  })
  const throwawayDeviceStore = createMemoryDeviceStore()
  const deviceSigner = createDeviceLeafSigner({
    signer: (await makeP256Ca('device-CA')).caSigner,
    issuer: 'CN=device-CA',
    caChainDer: [],
    sanBaseDomain: ZONE,
    trustDomain: TRUST_DOMAIN,
    devices: throwawayDeviceStore,
  })
  const renewer = createLeafRenewer({
    hostSigner,
    deviceSigner,
    deviceExpiry: createDeviceRegistry({ devices: throwawayDeviceStore }),
  })
  // The CURRENT cert = an initial leaf issued for this host (what the mTLS layer would present).
  const { cert: currentCertDer } = await hostSigner.signHostLeaf(host.hostId, spki, csr)
  return { hosts: reg, renewer, hostSigner, ca, subdomain, spki, keys, csr, currentCertDer, accountId, deviceSigner }
}

function hostDeps(ctx: HostCtx, extra?: Partial<RenewDeps>): RenewDeps {
  return {
    hosts: ctx.hosts,
    devices: createDeviceRegistry({ devices: createMemoryDeviceStore() }),
    renewer: ctx.renewer,
    ...extra,
  }
}

describe('A6 POST /renew (host, mTLS current cert)', () => {
  test('valid current cert + same-key CSR → 201 real X.509 leaf carrying the SAME subdomain', async () => {
    const ctx = await hostCtx('alice')
    const app = appWith(hostDeps(ctx))
    await app.ready()
    const res = await app.inject({
      method: 'POST',
      url: '/renew',
      headers: { 'x-client-cert': certHeader(ctx.currentCertDer) },
      payload: { csr: b64Csr(ctx.csr) },
    })
    expect(res.statusCode).toBe(201)
    const body = JSON.parse(res.body)
    expect(typeof body.notAfter).toBe('string')
    expect(Array.isArray(body.caChain)).toBe(true)
    const leaf = new x509.X509Certificate(new Uint8Array(Buffer.from(body.cert, 'base64')))
    const names = leaf.getExtension(x509.SubjectAlternativeNameExtension)!.names.toJSON()
    expect(names).toContainEqual({ type: 'dns', value: 'alice.terminal.yaojia.wang' })
  })

  test('missing current cert → 401', async () => {
    const ctx = await hostCtx('alice')
    const app = appWith(hostDeps(ctx))
    await app.ready()
    const res = await app.inject({ method: 'POST', url: '/renew', payload: { csr: b64Csr(ctx.csr) } })
    expect(res.statusCode).toBe(401)
  })

  test('malformed current cert bytes → 401', async () => {
    const ctx = await hostCtx('alice')
    const app = appWith(hostDeps(ctx))
    await app.ready()
    const res = await app.inject({
      method: 'POST',
      url: '/renew',
      headers: { 'x-client-cert': certHeader(Uint8Array.from([0, 1, 2, 3])) },
      payload: { csr: b64Csr(ctx.csr) },
    })
    expect(res.statusCode).toBe(401)
  })

  test('CSR with a DIFFERENT key (key-swap attempt) → 400 (MVP same-key)', async () => {
    const ctx = await hostCtx('alice')
    const app = appWith(hostDeps(ctx))
    await app.ready()
    const { der: otherCsr } = await buildCsrEc('CN=alice')
    const res = await app.inject({
      method: 'POST',
      url: '/renew',
      headers: { 'x-client-cert': certHeader(ctx.currentCertDer) },
      payload: { csr: b64Csr(otherCsr) },
    })
    expect(res.statusCode).toBe(400)
  })

  test('revoked host → 403', async () => {
    const ctx = await hostCtx('alice')
    const host = await ctx.hosts.getHostBySubdomain('alice')
    await ctx.hosts.setHostStatus(host!.hostId, 'revoked')
    const app = appWith(hostDeps(ctx))
    await app.ready()
    const res = await app.inject({
      method: 'POST',
      url: '/renew',
      headers: { 'x-client-cert': certHeader(ctx.currentCertDer) },
      payload: { csr: b64Csr(ctx.csr) },
    })
    expect(res.statusCode).toBe(403)
  })

  test('current cert whose subdomain is unknown to the registry → 403', async () => {
    const ctx = await hostCtx('alice')
    // Route uses a FRESH empty registry — the presented cert's subdomain resolves to nothing.
    const emptyStores = createMemoryStores()
    const app = appWith({
      hosts: createHostRegistry({ hosts: emptyStores.hosts }),
      devices: createDeviceRegistry({ devices: createMemoryDeviceStore() }),
      renewer: ctx.renewer,
    })
    await app.ready()
    const res = await app.inject({
      method: 'POST',
      url: '/renew',
      headers: { 'x-client-cert': certHeader(ctx.currentCertDer) },
      payload: { csr: b64Csr(ctx.csr) },
    })
    expect(res.statusCode).toBe(403)
  })

  test('a renewal CSR that requests a foreign subject is stamped with the REGISTRY subdomain (no smuggling)', async () => {
    const ctx = await hostCtx('alice')
    const app = appWith(hostDeps(ctx))
    await app.ready()
    // Same key, but a malicious subject label — the issuer must ignore it and stamp 'alice'.
    const evilCsr = new Uint8Array(
      (
        await x509.Pkcs10CertificateRequestGenerator.create({
          name: 'CN=bob.terminal.yaojia.wang',
          keys: ctx.keys,
          signingAlgorithm: { name: 'ECDSA', hash: 'SHA-256' },
        })
      ).rawData,
    )
    const res = await app.inject({
      method: 'POST',
      url: '/renew',
      headers: { 'x-client-cert': certHeader(ctx.currentCertDer) },
      payload: { csr: b64Csr(evilCsr) },
    })
    expect(res.statusCode).toBe(201)
    const leaf = new x509.X509Certificate(new Uint8Array(Buffer.from(JSON.parse(res.body).cert, 'base64')))
    const names = leaf.getExtension(x509.SubjectAlternativeNameExtension)!.names.toJSON()
    expect(names).toContainEqual({ type: 'dns', value: 'alice.terminal.yaojia.wang' })
    expect(names).not.toContainEqual({ type: 'dns', value: 'bob.terminal.yaojia.wang' })
  })

  test('over the per-identity renewal rate → 429', async () => {
    const ctx = await hostCtx('alice')
    const app = appWith(hostDeps(ctx, { rateLimiter: createRenewRateLimiter({ max: 1 }) }))
    await app.ready()
    const first = await app.inject({
      method: 'POST',
      url: '/renew',
      headers: { 'x-client-cert': certHeader(ctx.currentCertDer) },
      payload: { csr: b64Csr(ctx.csr) },
    })
    expect(first.statusCode).toBe(201)
    const second = await app.inject({
      method: 'POST',
      url: '/renew',
      headers: { 'x-client-cert': certHeader(ctx.currentCertDer) },
      payload: { csr: b64Csr(ctx.csr) },
    })
    expect(second.statusCode).toBe(429)
  })
})

// ── DEVICE /device/:id/renew ─────────────────────────────────────────────────────────────────────
interface DeviceCtx {
  devices: DeviceRegistry
  renewer: ReturnType<typeof createLeafRenewer>
  deviceSigner: DeviceLeafSigner
  ca: P256Ca
  deviceId: string
  spki: Uint8Array
  csr: Uint8Array
  currentCertDer: Uint8Array
}

async function registerDeviceLeaf(
  registry: DeviceRegistry,
  store: ReturnType<typeof createMemoryDeviceStore>,
  deviceSigner: DeviceLeafSigner,
  scope = 'alice',
) {
  const { der: csr, keys } = await buildCsrEc('CN=web-terminal-device')
  const spki = await spkiOf(keys.publicKey)
  const record = await registry.registerDevice({ accountId: randomUUID(), subdomainScope: scope, ecPubkeySpki: spki, attestationLevel: 'none' })
  const { cert: currentCertDer } = await deviceSigner.signDeviceLeaf(record.deviceId, csr)
  return { record, csr, spki, currentCertDer }
}

async function deviceCtx(scope = 'alice'): Promise<DeviceCtx> {
  const ca = await makeP256Ca('device-CA')
  const store = createMemoryDeviceStore()
  const registry = createDeviceRegistry({ devices: store })
  const deviceSigner = createDeviceLeafSigner({
    signer: ca.caSigner,
    issuer: ca.caCert.subjectName,
    caChainDer: [ca.caDer],
    sanBaseDomain: ZONE,
    trustDomain: TRUST_DOMAIN,
    devices: store,
  })
  const hostSigner = createFrpClientLeafSigner({
    hosts: createMemoryStores().hosts,
    signer: ca.caSigner,
    issuerName: 'CN=frp-client-CA',
    caChainDer: [],
    trustDomain: TRUST_DOMAIN,
    dnsZone: ZONE,
  })
  const renewer = createLeafRenewer({ hostSigner, deviceSigner, deviceExpiry: registry })
  const { record, csr, spki, currentCertDer } = await registerDeviceLeaf(registry, store, deviceSigner, scope)
  return { devices: registry, renewer, deviceSigner, ca, deviceId: record.deviceId, spki, csr, currentCertDer }
}

function deviceDeps(ctx: DeviceCtx): RenewDeps {
  return {
    hosts: createHostRegistry({ hosts: createMemoryStores().hosts }),
    devices: ctx.devices,
    renewer: ctx.renewer,
  }
}

describe('A6 POST /device/:id/renew (device, mTLS current cert)', () => {
  test('valid current cert + same-key CSR → 201 real X.509 leaf with the SAME subdomainScope', async () => {
    const ctx = await deviceCtx('alice')
    const app = appWith(deviceDeps(ctx))
    await app.ready()
    const res = await app.inject({
      method: 'POST',
      url: `/device/${ctx.deviceId}/renew`,
      headers: { 'x-client-cert': certHeader(ctx.currentCertDer) },
      payload: { csr: b64Csr(ctx.csr) },
    })
    expect(res.statusCode).toBe(201)
    const leaf = new x509.X509Certificate(new Uint8Array(Buffer.from(JSON.parse(res.body).cert, 'base64')))
    const names = leaf.getExtension(x509.SubjectAlternativeNameExtension)!.names.toJSON()
    expect(names).toContainEqual({ type: 'dns', value: 'alice.terminal.yaojia.wang' })
  })

  test('missing current cert → 401', async () => {
    const ctx = await deviceCtx('alice')
    const app = appWith(deviceDeps(ctx))
    await app.ready()
    const res = await app.inject({ method: 'POST', url: `/device/${ctx.deviceId}/renew`, payload: { csr: b64Csr(ctx.csr) } })
    expect(res.statusCode).toBe(401)
  })

  test('CSR with a DIFFERENT key → 400 (MVP same-key)', async () => {
    const ctx = await deviceCtx('alice')
    const app = appWith(deviceDeps(ctx))
    await app.ready()
    const { der: otherCsr } = await buildCsrEc('CN=web-terminal-device')
    const res = await app.inject({
      method: 'POST',
      url: `/device/${ctx.deviceId}/renew`,
      headers: { 'x-client-cert': certHeader(ctx.currentCertDer) },
      payload: { csr: b64Csr(otherCsr) },
    })
    expect(res.statusCode).toBe(400)
  })

  test("presenting device X's cert against a DIFFERENT device's :id → 403", async () => {
    const ctx = await deviceCtx('alice')
    // A second, distinct device in the same registry.
    const store = createMemoryDeviceStore()
    const registry = createDeviceRegistry({ devices: store })
    const otherSigner = createDeviceLeafSigner({
      signer: ctx.ca.caSigner,
      issuer: ctx.ca.caCert.subjectName,
      caChainDer: [ctx.ca.caDer],
      sanBaseDomain: ZONE,
      trustDomain: TRUST_DOMAIN,
      devices: store,
    })
    const other = await registerDeviceLeaf(registry, store, otherSigner, 'alice')
    // Route registry holds BOTH devices; present X's cert to Y's path.
    const merged = createMemoryDeviceStore()
    const mergedReg = createDeviceRegistry({ devices: merged })
    const recX = await mergedReg.registerDevice({ accountId: randomUUID(), subdomainScope: 'alice', ecPubkeySpki: ctx.spki, attestationLevel: 'none' })
    const recY = await mergedReg.registerDevice({ accountId: randomUUID(), subdomainScope: 'alice', ecPubkeySpki: other.spki, attestationLevel: 'none' })
    void recX
    const app = appWith({ hosts: createHostRegistry({ hosts: createMemoryStores().hosts }), devices: mergedReg, renewer: ctx.renewer })
    await app.ready()
    const res = await app.inject({
      method: 'POST',
      url: `/device/${recY.deviceId}/renew`, // Y's path
      headers: { 'x-client-cert': certHeader(ctx.currentCertDer) }, // X's cert (SPIFFE deviceId ≠ recY)
      payload: { csr: b64Csr(ctx.csr) },
    })
    expect(res.statusCode).toBe(403)
  })

  test('revoked device → 403', async () => {
    const ctx = await deviceCtx('alice')
    await ctx.devices.setDeviceStatus(ctx.deviceId, 'revoked')
    const app = appWith(deviceDeps(ctx))
    await app.ready()
    const res = await app.inject({
      method: 'POST',
      url: `/device/${ctx.deviceId}/renew`,
      headers: { 'x-client-cert': certHeader(ctx.currentCertDer) },
      payload: { csr: b64Csr(ctx.csr) },
    })
    expect(res.statusCode).toBe(403)
  })
})

// ── CP6a: bounded rate-limiter hits Map (memory-DoS guard) ─────────────────────────────────────────
describe('CP6a createRenewRateLimiter — bounded hits Map', () => {
  test('sweeps fully-expired identities so the Map does not grow unbounded', () => {
    let t = 0
    const rl = createRenewRateLimiter({ max: 5, windowMs: 1000, maxIdentities: 2, now: () => t })
    rl.check('a')
    rl.check('b')
    expect(rl.size()).toBe(2) // at the cap
    t = 5000 // a & b are now well past their 1s window
    rl.check('c') // inserting a new identity at the cap sweeps the two stale windows first
    expect(rl.size()).toBe(1) // only 'c' survives
  })

  test('caps tracked identities under a distinct-identity flood (all within window)', () => {
    const rl = createRenewRateLimiter({ max: 5, windowMs: 60_000, maxIdentities: 2, now: () => 0 })
    for (let i = 0; i < 100; i++) rl.check(`id-${i}`)
    expect(rl.size()).toBeLessThanOrEqual(2)
  })
})

// ── CP6b + CP6c: body size cap + presented-cert chain/expiry verification ───────────────────────────
describe('CP6b POST /renew — CSR length cap', () => {
  test('an over-long CSR is rejected by the body schema → 400', async () => {
    const ctx = await hostCtx('alice')
    const app = appWith(hostDeps(ctx))
    await app.ready()
    const res = await app.inject({
      method: 'POST',
      url: '/renew',
      headers: { 'x-client-cert': certHeader(ctx.currentCertDer) },
      payload: { csr: 'A'.repeat(9000) }, // > MAX_CSR_B64_LEN (8192)
    })
    expect(res.statusCode).toBe(400)
  })
})

describe('CP6c POST /renew — presented current-cert chain + expiry verification', () => {
  /** Mint a host leaf (SPIFFE host SAN, chained to the frp-client-CA) with a caller-chosen validity. */
  async function mintHostLeaf(ctx: HostCtx, notBefore: Date, notAfter: Date, signer = ctx.ca.caSigner): Promise<Uint8Array> {
    const spiffe = `spiffe://relay.terminal.yaojia.wang/account/${ctx.accountId}/host/${ctx.subdomain}`
    return assembleCertificate({
      subjectPublicKey: ctx.spki,
      subject: `CN=${ctx.subdomain}`,
      issuer: ctx.ca.caCert.subjectName,
      serialNumber: Uint8Array.from([0x09]),
      notBefore,
      notAfter,
      extensions: [
        new x509.SubjectAlternativeNameExtension([
          { type: 'dns', value: `${ctx.subdomain}.terminal.yaojia.wang` },
          { type: 'url', value: spiffe },
        ]),
        new x509.BasicConstraintsExtension(false, undefined, true),
        new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature, true),
        new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage.clientAuth]),
      ],
      signer,
      sigAlg: 'ecdsa-p256',
    })
  }

  test('a valid current cert that CHAINS to the supplied anchor still renews → 201 (no regression)', async () => {
    const ctx = await hostCtx('alice')
    const app = appWith(hostDeps(ctx, { hostCaAnchorsDer: [ctx.ca.caDer] }))
    await app.ready()
    const res = await app.inject({
      method: 'POST',
      url: '/renew',
      headers: { 'x-client-cert': certHeader(ctx.currentCertDer) },
      payload: { csr: b64Csr(ctx.csr) },
    })
    expect(res.statusCode).toBe(201)
  })

  test('an EXPIRED current cert (valid chain, notAfter in the past) is rejected → 401', async () => {
    const ctx = await hostCtx('alice')
    const now = Date.now()
    const expired = await mintHostLeaf(ctx, new Date(now - 2 * DAY_MS), new Date(now - DAY_MS))
    const app = appWith(hostDeps(ctx, { hostCaAnchorsDer: [ctx.ca.caDer] }))
    await app.ready()
    const res = await app.inject({
      method: 'POST',
      url: '/renew',
      headers: { 'x-client-cert': certHeader(expired) },
      payload: { csr: b64Csr(ctx.csr) },
    })
    // STRICT on this route: nginx would never forward an expired client cert here anyway, so the
    // deadlock escape hatch lives on `/recover` (below) and NOT on the mTLS renewal path.
    expect(res.statusCode).toBe(401)
  })

  test('a current cert signed by an UNTRUSTED CA (not in the anchor set) is rejected → 401', async () => {
    const ctx = await hostCtx('alice')
    const rogueCa = await makeP256Ca('rogue-CA')
    const now = Date.now()
    const rogue = await mintHostLeaf(ctx, new Date(now - 60_000), new Date(now + DAY_MS), rogueCa.caSigner)
    const app = appWith(hostDeps(ctx, { hostCaAnchorsDer: [ctx.ca.caDer] }))
    await app.ready()
    const res = await app.inject({
      method: 'POST',
      url: '/renew',
      headers: { 'x-client-cert': certHeader(rogue) },
      payload: { csr: b64Csr(ctx.csr) },
    })
    expect(res.statusCode).toBe(401)
  })
})

/**
 * CP6d · POST /recover — the expired-leaf escape hatch.
 *
 * Unlike `/renew` this route is NOT mTLS-authenticated: nginx cannot forward an expired client cert
 * (under `ssl_verify_client optional` it 400s, and `optional_no_ca` only tolerates CHAIN errors, not
 * `X509_V_ERR_CERT_HAS_EXPIRED`). The lapsed cert therefore arrives in the BODY, and the
 * control-plane becomes the sole verifier. These tests pin down that nothing except the `notAfter`
 * bound was relaxed — chain, SPIFFE, `notBefore`, and registry status all still decide.
 */
describe('CP6d POST /recover — expired-leaf recovery', () => {
  async function mintLeaf(
    ctx: HostCtx,
    notBefore: Date,
    notAfter: Date,
    signer = ctx.ca.caSigner,
  ): Promise<Uint8Array> {
    const spiffe = `spiffe://relay.terminal.yaojia.wang/account/${ctx.accountId}/host/${ctx.subdomain}`
    return assembleCertificate({
      subjectPublicKey: ctx.spki,
      subject: `CN=${ctx.subdomain}`,
      issuer: ctx.ca.caCert.subjectName,
      serialNumber: Uint8Array.from([0x0b]),
      notBefore,
      notAfter,
      extensions: [
        new x509.SubjectAlternativeNameExtension([
          { type: 'dns', value: `${ctx.subdomain}.terminal.yaojia.wang` },
          { type: 'url', value: spiffe },
        ]),
        new x509.BasicConstraintsExtension(false, undefined, true),
        new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature, true),
        new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage.clientAuth]),
      ],
      signer,
      sigAlg: 'ecdsa-p256',
    })
  }

  const b64 = (der: Uint8Array): string => Buffer.from(der).toString('base64')

  async function post(ctx: HostCtx, cert: Uint8Array, extra?: Partial<RenewDeps>) {
    const app = appWith(hostDeps(ctx, { hostCaAnchorsDer: [ctx.ca.caDer], ...extra }))
    await app.ready()
    return app.inject({
      method: 'POST',
      url: '/recover',
      payload: { cert: b64(cert), csr: b64Csr(ctx.csr) },
    })
  }

  test('a leaf expired INSIDE the grace window is re-issued → 201 (breaks the deadlock)', async () => {
    const ctx = await hostCtx('alice')
    const now = Date.now()
    const res = await post(ctx, await mintLeaf(ctx, new Date(now - 9 * DAY_MS), new Date(now - 8 * DAY_MS)))
    expect(res.statusCode).toBe(201)
    expect(JSON.parse(res.payload).cert).toBeTruthy()
  })

  test('a leaf expired BEYOND the grace window is refused → 401', async () => {
    const ctx = await hostCtx('alice')
    const now = Date.now()
    const res = await post(ctx, await mintLeaf(ctx, new Date(now - 60 * DAY_MS), new Date(now - 31 * DAY_MS)))
    expect(res.statusCode).toBe(401)
  })

  test('grace 0 disables recovery entirely → 401', async () => {
    const ctx = await hostCtx('alice')
    const now = Date.now()
    const res = await post(
      ctx,
      await mintLeaf(ctx, new Date(now - 2 * DAY_MS), new Date(now - DAY_MS)),
      { expiredRenewGraceMs: 0 },
    )
    expect(res.statusCode).toBe(401)
  })

  /**
   * THE load-bearing test for this route. nginx no longer validates the chain here, so a forged
   * self-signed cert carrying a correct-looking SPIFFE SAN must be rejected by the control-plane
   * alone. If this ever goes green-to-red, `/recover` becomes an unauthenticated cert vending machine.
   */
  test('a self-signed cert with a FORGED SPIFFE SAN is refused → 401', async () => {
    const ctx = await hostCtx('alice')
    const rogueCa = await makeP256Ca('rogue-CA')
    const now = Date.now()
    const rogue = await mintLeaf(
      ctx,
      new Date(now - 9 * DAY_MS),
      new Date(now - 8 * DAY_MS),
      rogueCa.caSigner,
    )
    const res = await post(ctx, rogue)
    expect(res.statusCode).toBe(401)
  })

  test('recovery NEVER bypasses revocation — revoked host → 403', async () => {
    const ctx = await hostCtx('alice')
    const now = Date.now()
    const cert = await mintLeaf(ctx, new Date(now - 9 * DAY_MS), new Date(now - 8 * DAY_MS))
    const host = await ctx.hosts.getHostBySubdomain('alice')
    await ctx.hosts.setHostStatus(host!.hostId, 'revoked')
    const res = await post(ctx, cert)
    expect(res.statusCode).toBe(403)
  })

  test('grace covers notAfter ONLY — a not-yet-valid leaf is refused → 401', async () => {
    const ctx = await hostCtx('alice')
    const now = Date.now()
    const res = await post(ctx, await mintLeaf(ctx, new Date(now + DAY_MS), new Date(now + 2 * DAY_MS)))
    expect(res.statusCode).toBe(401)
  })

  test('a still-valid leaf may also use /recover → 201', async () => {
    const ctx = await hostCtx('alice')
    const res = await post(ctx, ctx.currentCertDer)
    expect(res.statusCode).toBe(201)
  })

  test('a missing cert field is rejected → 400 (uniform schema reject, same as a missing csr)', async () => {
    const ctx = await hostCtx('alice')
    const app = appWith(hostDeps(ctx, { hostCaAnchorsDer: [ctx.ca.caDer] }))
    await app.ready()
    const res = await app.inject({ method: 'POST', url: '/recover', payload: { csr: b64Csr(ctx.csr) } })
    // The module answers with uniform rejects that never say which check failed: 400 for a malformed
    // body, 401 for a cert that parses but is not trusted, 403 for a trusted cert that is not allowed.
    expect(res.statusCode).toBe(400)
  })

  /**
   * The header is the mTLS terminator's channel. On `/recover` the cert comes from the body, so a
   * client-supplied header must not be able to substitute an identity.
   */
  test('an x-client-cert HEADER cannot override the body identity → 401', async () => {
    const ctx = await hostCtx('alice')
    const rogueCa = await makeP256Ca('rogue-CA')
    const now = Date.now()
    const rogue = await mintLeaf(ctx, new Date(now - 9 * DAY_MS), new Date(now - 8 * DAY_MS), rogueCa.caSigner)
    const app = appWith(hostDeps(ctx, { hostCaAnchorsDer: [ctx.ca.caDer] }))
    await app.ready()
    const res = await app.inject({
      method: 'POST',
      url: '/recover',
      headers: { 'x-client-cert': certHeader(ctx.currentCertDer) },
      payload: { cert: b64(rogue), csr: b64Csr(ctx.csr) },
    })
    expect(res.statusCode).toBe(401)
  })
})

/**
 * CP6e · POST /device/:id/recover — the phone-track counterpart of `/recover`.
 *
 * A device certificate hits the identical deadlock as a host leaf: `/device/:id/renew` is
 * mTLS-authenticated by the cert being renewed, and nginx refuses to forward an expired client cert
 * in any mode. Without this route a phone whose cert lapsed has to be re-enrolled by hand.
 */
describe('CP6e POST /device/:id/recover — expired device-cert recovery', () => {
  async function mintDeviceLeaf(
    ctx: DeviceCtx,
    accountId: string,
    notBefore: Date,
    notAfter: Date,
    signer = ctx.ca.caSigner,
  ): Promise<Uint8Array> {
    const spiffe = `spiffe://relay.terminal.yaojia.wang/account/${accountId}/device/${ctx.deviceId}`
    return assembleCertificate({
      subjectPublicKey: ctx.spki,
      subject: `CN=web-terminal-device`,
      issuer: ctx.ca.caCert.subjectName,
      serialNumber: Uint8Array.from([0x0d]),
      notBefore,
      notAfter,
      extensions: [
        new x509.SubjectAlternativeNameExtension([
          { type: 'dns', value: `alice.${ZONE}` },
          { type: 'url', value: spiffe },
        ]),
        new x509.BasicConstraintsExtension(false, undefined, true),
        new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature, true),
        new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage.clientAuth]),
      ],
      signer,
      sigAlg: 'ecdsa-p256',
    })
  }

  const b64 = (der: Uint8Array): string => Buffer.from(der).toString('base64')

  async function post(ctx: DeviceCtx, cert: Uint8Array, extra?: Partial<RenewDeps>) {
    const app = appWith({ ...deviceDeps(ctx), deviceCaAnchorsDer: [ctx.ca.caDer], ...extra })
    await app.ready()
    return app.inject({
      method: 'POST',
      url: `/device/${ctx.deviceId}/recover`,
      payload: { cert: b64(cert), csr: b64Csr(ctx.csr) },
    })
  }

  test('a device cert expired INSIDE the grace window is re-issued → 201', async () => {
    const ctx = await deviceCtx('alice')
    const rec = await ctx.devices.getDevice(ctx.deviceId)
    const now = Date.now()
    const res = await post(
      ctx,
      await mintDeviceLeaf(ctx, rec!.accountId, new Date(now - 9 * DAY_MS), new Date(now - 8 * DAY_MS)),
    )
    expect(res.statusCode).toBe(201)
    expect(JSON.parse(res.payload).cert).toBeTruthy()
  })

  test('a device cert expired BEYOND the grace window is refused → 401', async () => {
    const ctx = await deviceCtx('alice')
    const rec = await ctx.devices.getDevice(ctx.deviceId)
    const now = Date.now()
    const res = await post(
      ctx,
      await mintDeviceLeaf(ctx, rec!.accountId, new Date(now - 60 * DAY_MS), new Date(now - 31 * DAY_MS)),
    )
    expect(res.statusCode).toBe(401)
  })

  /** Same load-bearing check as the host route: nginx validates nothing on this path. */
  test('a self-signed cert with a FORGED device SPIFFE SAN is refused → 401', async () => {
    const ctx = await deviceCtx('alice')
    const rogueCa = await makeP256Ca('rogue-CA')
    const rec = await ctx.devices.getDevice(ctx.deviceId)
    const now = Date.now()
    const res = await post(
      ctx,
      await mintDeviceLeaf(
        ctx,
        rec!.accountId,
        new Date(now - 9 * DAY_MS),
        new Date(now - 8 * DAY_MS),
        rogueCa.caSigner,
      ),
    )
    expect(res.statusCode).toBe(401)
  })

  test('recovery NEVER bypasses revocation — revoked device → 403', async () => {
    const ctx = await deviceCtx('alice')
    const rec = await ctx.devices.getDevice(ctx.deviceId)
    const now = Date.now()
    const cert = await mintDeviceLeaf(ctx, rec!.accountId, new Date(now - 9 * DAY_MS), new Date(now - 8 * DAY_MS))
    await ctx.devices.setDeviceStatus(ctx.deviceId, 'revoked')
    const res = await post(ctx, cert)
    expect(res.statusCode).toBe(403)
  })

  test('the path :id must match the cert identity — no cross-device smuggling → 403', async () => {
    const ctx = await deviceCtx('alice')
    const rec = await ctx.devices.getDevice(ctx.deviceId)
    const now = Date.now()
    const cert = await mintDeviceLeaf(ctx, rec!.accountId, new Date(now - 9 * DAY_MS), new Date(now - 8 * DAY_MS))
    const app = appWith({ ...deviceDeps(ctx), deviceCaAnchorsDer: [ctx.ca.caDer] })
    await app.ready()
    const res = await app.inject({
      method: 'POST',
      url: `/device/someone-else/recover`,
      payload: { cert: b64(cert), csr: b64Csr(ctx.csr) },
    })
    expect(res.statusCode).toBe(403)
  })

  test('grace 0 disables device recovery entirely → 401', async () => {
    const ctx = await deviceCtx('alice')
    const rec = await ctx.devices.getDevice(ctx.deviceId)
    const now = Date.now()
    const res = await post(
      ctx,
      await mintDeviceLeaf(ctx, rec!.accountId, new Date(now - 2 * DAY_MS), new Date(now - DAY_MS)),
      { expiredRenewGraceMs: 0 },
    )
    expect(res.statusCode).toBe(401)
  })
})
