import { describe, expect, it } from 'vitest'
import {
  ContractDecodeError,
  ContractEncodeError,
  decodeHeader,
  decodeMuxFrame,
  encodeMuxFrame,
  type MuxFrameHeader,
} from '../src/index.js'

const baseHeader = (over: Partial<MuxFrameHeader> = {}): MuxFrameHeader => ({
  version: 1,
  type: 'data',
  fin: false,
  rst: false,
  streamId: 1,
  payloadLen: 0,
  ...over,
})

describe('§4.1 mux frame codec', () => {
  it('KAT: encodes a known DATA frame to a fixed byte vector', () => {
    const header = baseHeader({ type: 'data', fin: true, streamId: 0x01020304, payloadLen: 2 })
    const payload = Uint8Array.of(0xaa, 0xbb)
    const bytes = encodeMuxFrame(header, payload)
    expect([...bytes]).toEqual([
      0x01, // version
      0x02, // type=data
      0x01, // flags: FIN
      0x01, 0x02, 0x03, 0x04, // streamId
      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // payloadLen uint64
      0xaa, 0xbb, // payload
    ])
  })

  it('KAT: fixed bytes decode to fixed header + payload', () => {
    const bytes = Uint8Array.of(
      0x01, 0x07, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x99,
    )
    const { header, payload } = decodeMuxFrame(bytes)
    expect(header).toEqual({
      version: 1,
      type: 'goaway',
      fin: false,
      rst: true,
      streamId: 0,
      payloadLen: 1,
    })
    expect([...payload]).toEqual([0x99])
  })

  it.each(['open', 'data', 'close', 'ping', 'pong', 'windowUpdate', 'goaway'] as const)(
    'round-trips type=%s with flags',
    (type) => {
      const header = baseHeader({ type, fin: true, rst: true, streamId: 42, payloadLen: 3 })
      const payload = Uint8Array.of(1, 2, 3)
      const decoded = decodeMuxFrame(encodeMuxFrame(header, payload))
      expect(decoded.header).toEqual(header)
      expect([...decoded.payload]).toEqual([1, 2, 3])
    },
  )

  it('decodeHeader reads only the header of a longer buffer', () => {
    const bytes = encodeMuxFrame(baseHeader({ payloadLen: 4 }), Uint8Array.of(9, 9, 9, 9))
    expect(decodeHeader(bytes).payloadLen).toBe(4)
  })

  it('rejects encode when payloadLen != payload.length', () => {
    expect(() => encodeMuxFrame(baseHeader({ payloadLen: 5 }), Uint8Array.of(1))).toThrow(
      ContractEncodeError,
    )
  })

  it('rejects an unknown frame type code on decode', () => {
    const bytes = Uint8Array.of(0x01, 0x7f, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
    expect(() => decodeHeader(bytes)).toThrow(ContractDecodeError)
  })

  it('rejects reserved flag bits', () => {
    const bytes = Uint8Array.of(0x01, 0x02, 0x04, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
    expect(() => decodeHeader(bytes)).toThrow(/reserved flag/)
  })

  it('rejects an unsupported version', () => {
    const bytes = Uint8Array.of(0x02, 0x02, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
    expect(() => decodeHeader(bytes)).toThrow(/version/)
  })

  it('rejects a truncated payload', () => {
    const bytes = Uint8Array.of(0x01, 0x02, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x05)
    expect(() => decodeMuxFrame(bytes)).toThrow(/truncated/)
  })

  it('rejects a buffer shorter than the header', () => {
    expect(() => decodeHeader(Uint8Array.of(0x01, 0x02))).toThrow(ContractDecodeError)
  })
})
