import { describe, it, expect, beforeEach } from 'vitest'
import {
  signDeviceAuthProof,
  verifyDeviceProof,
  deviceProofAccount,
} from '../src/capability/device-proof.js'
import { setupP5SigningKey, makeEphemeral, principal } from './_helpers.js'
import { randomBytes } from 'node:crypto'

const NOW = 1_700_000_000

async function bindingA() {
  return {
    clientEphPub: new Uint8Array(randomBytes(32)),
    clientNonce: new Uint8Array(randomBytes(24)),
  }
}

describe('device-auth proof (§4.4 / §6b)', () => {
  let signingKey: CryptoKey
  beforeEach(async () => {
    ;({ signingKey } = await setupP5SigningKey())
  })

  it('verifies a proof bound to THIS handshake (accountId asserted)', async () => {
    const binding = await bindingA()
    const proof = await signDeviceAuthProof(principal('acct-A'), binding, signingKey, NOW)
    expect(await verifyDeviceProof(proof, binding, NOW)).toBe(true)
    expect(deviceProofAccount(proof)).toBe('acct-A')
  })

  it('rejects a proof replayed into a DIFFERENT handshake (different clientEphPub)', async () => {
    const bindingA1 = await bindingA()
    const proof = await signDeviceAuthProof(principal('acct-A'), bindingA1, signingKey, NOW)
    const bindingB = await bindingA() // fresh ephemeral / nonce
    expect(await verifyDeviceProof(proof, bindingB, NOW)).toBe(false)
  })

  it('rejects a proof signed by a non-P5 key (unbound/forged bearer)', async () => {
    const binding = await bindingA()
    const other = await makeEphemeral()
    const proof = await signDeviceAuthProof(principal('acct-A'), binding, other.privateKey, NOW)
    // verify key is still P5's key from setup; other.privateKey is not P5's signer
    expect(await verifyDeviceProof(proof, binding, NOW)).toBe(false)
  })

  it('rejects a tampered proof', async () => {
    const binding = await bindingA()
    const proof = await signDeviceAuthProof(principal('acct-A'), binding, signingKey, NOW)
    const tampered = proof.slice(0, -2) + (proof.endsWith('AA') ? 'BB' : 'AA')
    expect(await verifyDeviceProof(tampered, binding, NOW)).toBe(false)
  })

  it('rejects a stale proof (outside freshness window)', async () => {
    const binding = await bindingA()
    const proof = await signDeviceAuthProof(principal('acct-A'), binding, signingKey, NOW)
    expect(await verifyDeviceProof(proof, binding, NOW + 10_000)).toBe(false)
  })
})
