import { describe, expect, it } from 'vitest'
import type { AeadAlg } from 'relay-contracts'
import { aeadOpen, aeadSeal, importAeadKey, nonceLength, tagLength } from '../src/aead.js'
import { AeadOpenError, E2EError } from '../src/errors.js'
import { bytesToHex, hexToBytes } from './helpers.js'
import aeadVectors from './vectors/aead.json' with { type: 'json' }

const ALGS: AeadAlg[] = ['aes-256-gcm', 'xchacha20-poly1305']

describe('T2 aead', () => {
  it('nonceLength / tagLength per alg (§4.4)', () => {
    expect(nonceLength('aes-256-gcm')).toBe(12)
    expect(nonceLength('xchacha20-poly1305')).toBe(24)
    expect(tagLength('aes-256-gcm')).toBe(16)
  })

  it('KAT: each frozen vector seals to the expected ciphertext+tag and opens back', () => {
    for (const v of aeadVectors) {
      const key = importAeadKey(hexToBytes(v.key), v.alg as AeadAlg, 'c2h')
      const sealed = aeadSeal(
        key,
        hexToBytes(v.nonce),
        hexToBytes(v.plaintext),
        hexToBytes(v.aad),
      )
      expect(bytesToHex(sealed.ciphertext)).toBe(v.ciphertext)
      expect(bytesToHex(sealed.tag)).toBe(v.tag)
      const opened = aeadOpen(key, hexToBytes(v.nonce), sealed.ciphertext, sealed.tag, hexToBytes(v.aad))
      expect(bytesToHex(opened)).toBe(v.plaintext)
    }
  })

  it.each(ALGS)('round-trips empty and 1 MiB plaintext (%s)', (alg) => {
    const key = importAeadKey(new Uint8Array(32).fill(9), alg, 'c2h')
    const nonce = new Uint8Array(nonceLength(alg)).fill(1)
    const aad = new Uint8Array([7, 7])
    for (const pt of [new Uint8Array(0), new Uint8Array(1024 * 1024).fill(0xa5)]) {
      const { ciphertext, tag } = aeadSeal(key, nonce, pt, aad)
      const opened = aeadOpen(key, nonce, ciphertext, tag, aad)
      expect(opened).toEqual(pt)
    }
  })

  it.each(ALGS)('INV13 substrate: cipher-bit / aad-byte / wrong-key tamper → AeadOpenError (%s)', (alg) => {
    const key = importAeadKey(new Uint8Array(32).fill(3), alg, 'c2h')
    const wrong = importAeadKey(new Uint8Array(32).fill(4), alg, 'c2h')
    const nonce = new Uint8Array(nonceLength(alg)).fill(2)
    const aad = new Uint8Array([1, 2, 3])
    const { ciphertext, tag } = aeadSeal(key, nonce, new TextEncoder().encode('secret'), aad)

    const flippedCt = ciphertext.slice()
    flippedCt[0]! ^= 0x01
    expect(() => aeadOpen(key, nonce, flippedCt, tag, aad)).toThrow(AeadOpenError)

    const flippedAad = aad.slice()
    flippedAad[0]! ^= 0x01
    expect(() => aeadOpen(key, nonce, ciphertext, tag, flippedAad)).toThrow(AeadOpenError)

    expect(() => aeadOpen(wrong, nonce, ciphertext, tag, aad)).toThrow(AeadOpenError)
  })

  it('nonce width mismatch → typed error, not silent truncation', () => {
    const key = importAeadKey(new Uint8Array(32).fill(1), 'xchacha20-poly1305', 'c2h')
    expect(() => aeadSeal(key, new Uint8Array(12), new Uint8Array(1), new Uint8Array(0))).toThrow(
      E2EError,
    )
  })

  it('importAeadKey rejects a non-32-byte key', () => {
    expect(() => importAeadKey(new Uint8Array(16), 'aes-256-gcm', '')).toThrow(E2EError)
  })
})
