import { describe, test, expect, vi } from 'vitest'
import { createMemoryStores } from '../src/store/memory.js'
import { createHostRegistry } from '../src/registry/hosts.js'
import { createLeafSigner, LeafSignError } from '../src/ca/sign.js'
import { inProcessCaSigner } from '../src/boot/ca-wiring.js'
import { buildCsr, verifyCsrPoP } from '../src/ca/csr.js'
import { fingerprint } from '../src/ca/fingerprint.js'
import { generateEd25519, sealToX25519, openFromX25519, generateX25519 } from '../src/util/crypto.js'
import { randomBytes } from 'node:crypto'

function boundHost() {
  const stores = createMemoryStores()
  const hosts = createHostRegistry({ hosts: stores.hosts })
  return { stores, hosts }
}

describe('T8 signHostLeaf — INV14 registry-gated + CSR PoP', () => {
  test('active registered host + valid CSR → cert signed under CA', async () => {
    const { stores, hosts } = boundHost()
    const { publicKeyRaw, privateKey } = generateEd25519()
    const host = await hosts.bindHost({ accountId: '11111111-1111-4111-8111-111111111111', subdomain: 'alice', agentPubkey: publicKeyRaw, enrollFpr: fingerprint(publicKeyRaw) })
    const signer = createLeafSigner({ hosts: stores.hosts, signer: inProcessCaSigner(), caChainDer: [] })
    const { cert } = await signer.signHostLeaf(host.hostId, publicKeyRaw, buildCsr(privateKey, publicKeyRaw))
    expect(cert.length).toBeGreaterThan(0)
  })

  test('CSR proof-of-possession fails even for a registered active key → reject, KMS never called', async () => {
    const { stores, hosts } = boundHost()
    const { publicKeyRaw } = generateEd25519()
    const host = await hosts.bindHost({ accountId: '11111111-1111-4111-8111-111111111111', subdomain: 'alice', agentPubkey: publicKeyRaw, enrollFpr: fingerprint(publicKeyRaw) })
    const spy = inProcessCaSigner()
    const signSpy = vi.spyOn(spy, 'sign')
    const signer = createLeafSigner({ hosts: stores.hosts, signer: spy, caChainDer: [] })
    // forge a CSR: correct embedded pub but a garbage signature
    const forged = new Uint8Array(96)
    forged.set(publicKeyRaw, 0)
    forged.set(randomBytes(64), 32)
    expect((await verifyCsrPoP(forged)).ok).toBe(false)
    await expect(signer.signHostLeaf(host.hostId, publicKeyRaw, forged)).rejects.toBeInstanceOf(LeafSignError)
    expect(signSpy).not.toHaveBeenCalled()
  })

  test('INV14 gate: unregistered pubkey → throws, KMS sign never invoked', async () => {
    const { stores } = boundHost()
    const { publicKeyRaw, privateKey } = generateEd25519()
    const spy = inProcessCaSigner()
    const signSpy = vi.spyOn(spy, 'sign')
    const signer = createLeafSigner({ hosts: stores.hosts, signer: spy, caChainDer: [] })
    await expect(signer.signHostLeaf('00000000-0000-4000-8000-000000000000', publicKeyRaw, buildCsr(privateKey, publicKeyRaw))).rejects.toBeInstanceOf(LeafSignError)
    expect(signSpy).not.toHaveBeenCalled()
  })

  test('revoked host cannot be signed (INV12+INV14)', async () => {
    const { stores, hosts } = boundHost()
    const { publicKeyRaw, privateKey } = generateEd25519()
    const host = await hosts.bindHost({ accountId: '11111111-1111-4111-8111-111111111111', subdomain: 'alice', agentPubkey: publicKeyRaw, enrollFpr: fingerprint(publicKeyRaw) })
    await hosts.setHostStatus(host.hostId, 'revoked')
    const signer = createLeafSigner({ hosts: stores.hosts, signer: inProcessCaSigner(), caChainDer: [] })
    await expect(signer.signHostLeaf(host.hostId, publicKeyRaw, buildCsr(privateKey, publicKeyRaw))).rejects.toBeInstanceOf(LeafSignError)
  })
})

describe('host content-secret wrap (FIX 3 security property)', () => {
  test('only the matching private key can unwrap; raw secret absent from blob', () => {
    const recipient = generateX25519()
    const secret = new Uint8Array(randomBytes(32))
    const blob = sealToX25519(recipient.publicKeyRaw, secret)
    expect(Buffer.from(blob).includes(Buffer.from(secret))).toBe(false) // raw secret not in ciphertext
    expect(Buffer.from(openFromX25519(recipient.privateKey, blob)).equals(Buffer.from(secret))).toBe(true)
    const wrong = generateX25519()
    expect(() => openFromX25519(wrong.privateKey, blob)).toThrow()
  })
})
