/**
 * A3 — REAL capability verifier (boot/verifier.ts) backed by relay-auth's async `verifyCapabilityToken`.
 * Mints tokens with the paired Ed25519 private key via P5's `issueCapabilityToken`, configures the
 * matching public key, and asserts: a good token verifies; wrong `aud`, past `exp`, and a token
 * signed by a NON-matching key all reject (deny-by-default, INV6).
 */
import { describe, test, expect, beforeAll } from 'vitest'
import { issueCapabilityToken } from 'relay-auth'
import type { AuthenticatedPrincipal } from 'relay-auth'
import { encodeBase64UrlBytes } from 'relay-contracts'
import { createCapabilityVerifier, configureCapabilityVerifyKey } from '../src/boot/verifier.js'
import type { CapabilityVerifier } from '../src/api/authz.js'

const ACCOUNT_A = '11111111-1111-4111-8111-111111111111'
const AUD = 'term.example.com'
const NOW = 1_000_000
// A base64url SHA-256 JWK thumbprint is exactly 43 chars of [A-Za-z0-9_-]; 32 bytes → 43 base64url chars.
const CNF_JKT = encodeBase64UrlBytes(new Uint8Array(32).fill(7))

const principal: AuthenticatedPrincipal = {
  kind: 'human',
  accountId: ACCOUNT_A,
  principalId: 'cred-1',
  amr: ['passkey'],
  authAt: NOW - 10,
  stepUpAt: null,
}

type KeyPair = { publicKey: CryptoKey; privateKey: CryptoKey }
async function genKeyPair(): Promise<KeyPair> {
  // generateKey's named-algorithm overload is typed as CryptoKey; Ed25519 yields a pair at runtime.
  return (await globalThis.crypto.subtle.generateKey({ name: 'Ed25519' }, true, ['sign', 'verify'])) as unknown as KeyPair
}

function mint(signingKey: CryptoKey, now: number): Promise<string> {
  return issueCapabilityToken(
    { principal, aud: AUD, host: 'host-1', rights: ['manage'], ttlSeconds: 60, cnfJkt: CNF_JKT },
    signingKey,
    now,
  )
}

describe('A3 real capability verifier (relay-auth verifyCapabilityToken)', () => {
  let signingKey: CryptoKey
  let verifier: CapabilityVerifier

  beforeAll(async () => {
    const pair = await genKeyPair()
    signingKey = pair.privateKey
    const rawPub = new Uint8Array(await globalThis.crypto.subtle.exportKey('raw', pair.publicKey))
    await configureCapabilityVerifyKey(rawPub)
    verifier = createCapabilityVerifier()
  })

  test('verifies a token signed by the configured key and returns the account principal', async () => {
    const raw = await mint(signingKey, NOW)
    const token = await verifier.verify(raw, AUD, NOW)
    expect(token.sub).toBe(ACCOUNT_A)
    expect(token.aud).toBe(AUD)
    expect(token.rights).toContain('manage')
  })

  test('rejects a token minted for a different aud (Host-confusion guard)', async () => {
    const raw = await mint(signingKey, NOW)
    await expect(verifier.verify(raw, 'evil.example.com', NOW)).rejects.toThrow()
  })

  test('rejects an expired token', async () => {
    const raw = await mint(signingKey, NOW) // exp = NOW + 60
    await expect(verifier.verify(raw, AUD, NOW + 120)).rejects.toThrow()
  })

  test('rejects a token signed by a NON-matching key (bad signature)', async () => {
    const other = await genKeyPair()
    const raw = await mint(other.privateKey, NOW)
    await expect(verifier.verify(raw, AUD, NOW)).rejects.toThrow()
  })
})
