import { describe, expect, it } from 'vitest'
import {
  ContractDecodeError,
  NONCE_BYTES,
  decodeEnvelope,
  encodeEnvelope,
  nonceForSeq,
  type E2EEnvelope,
} from '../src/index.js'

const sample = (over: Partial<E2EEnvelope> = {}): E2EEnvelope => ({
  seq: 1n,
  nonce: nonceForSeq(1n, 'aes-256-gcm'),
  ciphertext: Uint8Array.of(0xde, 0xad, 0xbe, 0xef),
  tag: new Uint8Array(16).fill(0x11),
  ...over,
})

describe('§4.4 deterministic nonce = f(seq)', () => {
  it('KAT: seq 0x0102 in the last 8 bytes of a 12-byte GCM nonce', () => {
    expect([...nonceForSeq(0x0102n, 'aes-256-gcm')]).toEqual([
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01, 0x02,
    ])
  })

  it('produces the correct width per alg', () => {
    expect(nonceForSeq(1n, 'aes-256-gcm').length).toBe(NONCE_BYTES['aes-256-gcm'])
    expect(nonceForSeq(1n, 'xchacha20-poly1305').length).toBe(NONCE_BYTES['xchacha20-poly1305'])
  })

  it('is deterministic and collision-free across distinct seq', () => {
    const a = nonceForSeq(5n, 'xchacha20-poly1305')
    const b = nonceForSeq(5n, 'xchacha20-poly1305')
    const c = nonceForSeq(6n, 'xchacha20-poly1305')
    expect([...a]).toEqual([...b])
    expect([...a]).not.toEqual([...c])
  })
})

describe('§4.4 E2EEnvelope binary codec', () => {
  it('KAT: encodes a known small envelope to a fixed byte vector', () => {
    const env: E2EEnvelope = {
      seq: 1n,
      nonce: Uint8Array.of(1, 2, 3),
      ciphertext: Uint8Array.of(0xaa),
      tag: Uint8Array.of(0xbb),
    }
    expect([...encodeEnvelope(env)]).toEqual([
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // seq
      0x03, // nonceLen
      0x00, 0x00, 0x00, 0x01, // ciphertextLen
      0x01, // tagLen
      0x01, 0x02, 0x03, // nonce
      0xaa, // ciphertext
      0xbb, // tag
    ])
  })

  it('round-trips a realistic envelope (identity)', () => {
    const env = sample()
    const decoded = decodeEnvelope(encodeEnvelope(env))
    expect(decoded.seq).toBe(env.seq)
    expect([...decoded.nonce]).toEqual([...env.nonce])
    expect([...decoded.ciphertext]).toEqual([...env.ciphertext])
    expect([...decoded.tag]).toEqual([...env.tag])
  })

  it('preserves a large 64-bit seq', () => {
    const env = sample({ seq: 0xfffffffffffffff0n })
    expect(decodeEnvelope(encodeEnvelope(env)).seq).toBe(0xfffffffffffffff0n)
  })

  it('rejects a truncated envelope header', () => {
    expect(() => decodeEnvelope(Uint8Array.of(0, 1, 2))).toThrow(ContractDecodeError)
  })

  it('rejects a length mismatch (trailing/short bytes)', () => {
    const bytes = encodeEnvelope(sample())
    expect(() => decodeEnvelope(bytes.subarray(0, bytes.length - 1))).toThrow(/length mismatch/)
  })
})
