/**
 * A2-prep — file-backed KMS resolver unit tests. Proves `fileKmsResolver` loads an on-disk PKCS#8
 * PEM P-256 CA key and exposes it as a `CaSigner` whose signature verifies against the key's public
 * half (the single-owner file-custody reality of the VPS deploy), and fails loud on bad material
 * (missing file, malformed PEM, wrong curve) — NEVER leaking key bytes (INV9).
 */
import { describe, test, expect, beforeEach, afterEach } from 'vitest'
import { generateKeyPairSync, createPublicKey, verify as nodeVerify, randomBytes } from 'node:crypto'
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileKmsResolver, fileKeyRef } from '../src/boot/ca-wiring.js'

let dir: string

beforeEach(() => {
  dir = mkdtempSync(join(tmpdir(), 'cp-cawiring-'))
})
afterEach(() => {
  rmSync(dir, { recursive: true, force: true })
})

/** Write a fresh P-256 PKCS#8 PEM key to disk; return its path + the matching public KeyObject. */
function writeP256Key(name: string): { path: string; publicKey: ReturnType<typeof createPublicKey> } {
  const { privateKey, publicKey } = generateKeyPairSync('ec', { namedCurve: 'P-256' })
  const pem = privateKey.export({ format: 'pem', type: 'pkcs8' }) as string
  const path = join(dir, name)
  writeFileSync(path, pem)
  return { path, publicKey }
}

describe('fileKmsResolver — P-256 on-disk key custody', () => {
  test('resolves a file: ref, loads the P-256 key, and signs verifiably', async () => {
    const { path, publicKey } = writeP256Key('frp.key.pem')
    const resolver = fileKmsResolver()

    const { signer, policyRestrictedToServicePrincipal } = await resolver.resolve(fileKeyRef(path))
    expect(policyRestrictedToServicePrincipal).toBe(true)

    const tbs = randomBytes(48)
    const sig = await signer.sign(new Uint8Array(tbs))
    // signer emits raw P1363 r||s (64 bytes) — the same shape hardware/`inProcessP256CaSigner` produce.
    expect(sig.length).toBe(64)
    const ok = nodeVerify('sha256', tbs, { key: publicKey, dsaEncoding: 'ieee-p1363' }, Buffer.from(sig))
    expect(ok).toBe(true)
    // the signer's public key equals the on-disk key's SPKI DER.
    const spki = new Uint8Array(publicKey.export({ format: 'der', type: 'spki' }) as Buffer)
    expect(Buffer.from(signer.publicKeyRaw).equals(Buffer.from(spki))).toBe(true)
  })

  test('resolves a bare path (no file: prefix)', async () => {
    const { path, publicKey } = writeP256Key('bare.key.pem')
    const { signer } = await fileKmsResolver().resolve(path)
    const tbs = randomBytes(32)
    const sig = await signer.sign(new Uint8Array(tbs))
    expect(nodeVerify('sha256', tbs, { key: publicKey, dsaEncoding: 'ieee-p1363' }, Buffer.from(sig))).toBe(true)
  })

  test('missing file → rejects, never echoing the path contents', async () => {
    await expect(fileKmsResolver().resolve(fileKeyRef(join(dir, 'nope.pem')))).rejects.toThrow(/unreadable/)
  })

  test('malformed PEM → rejects', async () => {
    const path = join(dir, 'bad.pem')
    writeFileSync(path, 'not a pem key')
    await expect(fileKmsResolver().resolve(fileKeyRef(path))).rejects.toThrow(/not a valid PEM private key/)
  })

  test('non-P-256 key (Ed25519) → rejected (curve mismatch, fail-closed)', async () => {
    const { privateKey } = generateKeyPairSync('ed25519')
    const path = join(dir, 'ed.key.pem')
    writeFileSync(path, privateKey.export({ format: 'pem', type: 'pkcs8' }) as string)
    await expect(fileKmsResolver().resolve(fileKeyRef(path))).rejects.toThrow(/P-256/)
  })

  test('wrong-curve EC key (P-384) → rejected', async () => {
    const { privateKey } = generateKeyPairSync('ec', { namedCurve: 'P-384' })
    const path = join(dir, 'p384.key.pem')
    writeFileSync(path, privateKey.export({ format: 'pem', type: 'pkcs8' }) as string)
    await expect(fileKmsResolver().resolve(fileKeyRef(path))).rejects.toThrow(/P-256/)
  })

  test('empty file: ref path → rejected', async () => {
    await expect(fileKmsResolver().resolve('file:')).rejects.toThrow(/empty path/)
  })

  test('never leaks key material in the error message (INV9)', async () => {
    const { privateKey } = generateKeyPairSync('ec', { namedCurve: 'P-384' })
    const pem = privateKey.export({ format: 'pem', type: 'pkcs8' }) as string
    const path = join(dir, 'secret.key.pem')
    writeFileSync(path, pem)
    try {
      await fileKmsResolver().resolve(fileKeyRef(path))
      throw new Error('expected rejection')
    } catch (e) {
      expect(e instanceof Error ? e.message : '').not.toContain(pem.slice(40, 80))
    }
  })
})
