import { describe, expect, it } from 'vitest'
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { createPublicKey, verify } from 'node:crypto'
import {
  computeEnrollFpr,
  generateIdentity,
  generateP256Identity,
  identityFromPrivatePem,
  p256IdentityFromPrivatePem,
  verifySignature,
} from '../src/keys/identity.js'

describe('AgentIdentity (INV4)', () => {
  it('generates distinct keypairs', () => {
    const a = generateIdentity()
    const b = generateIdentity()
    expect(Buffer.from(a.publicKey).equals(Buffer.from(b.publicKey))).toBe(false)
    expect(a.publicKey.length).toBe(32)
  })

  it('sign/verify round-trips', () => {
    const id = generateIdentity()
    const msg = new TextEncoder().encode('transcript-bytes')
    const sig = id.sign(msg)
    expect(verifySignature(id.publicKey, msg, sig)).toBe(true)
    expect(verifySignature(id.publicKey, new TextEncoder().encode('tampered'), sig)).toBe(false)
  })

  it('enrollFpr is deterministic base64url(SHA-256(pubkey))', () => {
    const id = generateIdentity()
    expect(computeEnrollFpr(id.publicKey)).toBe(id.enrollFpr)
    expect(id.enrollFpr).not.toMatch(/[+/=]/) // base64url alphabet only
  })

  it('reloads the same identity from PEM', () => {
    const id = generateIdentity()
    const pem = id.exportPrivatePkcs8Pem()
    const reloaded = identityFromPrivatePem(pem)
    expect(Buffer.from(reloaded.publicKey).equals(Buffer.from(id.publicKey))).toBe(true)
    expect(reloaded.enrollFpr).toBe(id.enrollFpr)
  })

  it('security: no API returns raw private-key bytes', () => {
    const id = generateIdentity()
    // The only private-key surface is a PEM export for the 0600 keystore and an in-process
    // KeyObject; there is NO Uint8Array/raw-bytes getter for the private key.
    expect('privateKey' in id).toBe(false)
    const src = readFileSync(
      join(import.meta.dirname, '..', 'src', 'keys', 'identity.ts'),
      'utf8',
    )
    // No function exports raw private key bytes onto the network surface.
    expect(src).not.toMatch(/exportPrivateRaw|privateKeyBytes|toRawPrivate/)
  })

  it('keeps the Ed25519 alg tag (no P-256 regression)', () => {
    expect(generateIdentity().alg).toBe('ed25519')
  })
})

describe('P-256 AgentIdentity (FIX H-host-2 — native frp-client key)', () => {
  it('generates distinct EC P-256 keypairs tagged alg=p256', () => {
    const a = generateP256Identity()
    const b = generateP256Identity()
    expect(a.alg).toBe('p256')
    expect(Buffer.from(a.publicKey).equals(Buffer.from(b.publicKey))).toBe(false)
    // publicKey is the EC SubjectPublicKeyInfo DER (outer SEQUENCE), importable as a public key.
    expect(a.publicKey[0]).toBe(0x30)
    const pub = createPublicKey({ key: Buffer.from(a.publicKey), format: 'der', type: 'spki' })
    expect(pub.asymmetricKeyType).toBe('ec')
    expect(pub.asymmetricKeyDetails?.namedCurve).toBe('prime256v1')
  })

  it('sign produces a DER ECDSA signature that verifies under SHA-256', () => {
    const id = generateP256Identity()
    const msg = new TextEncoder().encode('certificationRequestInfo-bytes')
    const sig = id.sign(msg)
    const pub = createPublicKey({ key: Buffer.from(id.publicKey), format: 'der', type: 'spki' })
    expect(verify('sha256', msg, pub, sig)).toBe(true)
    expect(verify('sha256', new TextEncoder().encode('tampered'), pub, sig)).toBe(false)
  })

  it('enrollFpr is deterministic base64url(SHA-256(spki))', () => {
    const id = generateP256Identity()
    expect(computeEnrollFpr(id.publicKey)).toBe(id.enrollFpr)
    expect(id.enrollFpr).not.toMatch(/[+/=]/) // base64url alphabet only
  })

  it('reloads the same P-256 identity from PEM (keystore load path)', () => {
    const id = generateP256Identity()
    const pem = id.exportPrivatePkcs8Pem()
    const reloaded = p256IdentityFromPrivatePem(pem)
    expect(reloaded.alg).toBe('p256')
    expect(Buffer.from(reloaded.publicKey).equals(Buffer.from(id.publicKey))).toBe(true)
    expect(reloaded.enrollFpr).toBe(id.enrollFpr)
  })

  it('security: P-256 identity exposes no raw private-key getter', () => {
    const id = generateP256Identity()
    expect('privateKey' in id).toBe(false)
    // the PEM export is the only serialization surface; it is a PRIVATE key PEM (0600 keystore only).
    expect(id.exportPrivatePkcs8Pem()).toContain('PRIVATE KEY')
  })
})
