import { describe, test, expect } from 'bun:test';
import {
  encodeStreamOpen,
  encodeStreamOpenAck,
  decodeStreamHandshake,
  encodeStreamFrame,
  decodeStreamFrame,
  inspectEnvelopeType,
} from '../src/index.js';
import type { RatchetMessage } from '@shade/core';

function randBytes(n: number): Uint8Array {
  const buf = new Uint8Array(n);
  crypto.getRandomValues(buf);
  return buf;
}

function makeRatchetMessage(): RatchetMessage {
  return {
    dhPublicKey: randBytes(32),
    previousCounter: 3,
    counter: 9001,
    ciphertext: randBytes(128),
    nonce: randBytes(12),
  };
}

describe('Stream sub-session wire (V4.11)', () => {
  test('STREAM_OPEN round-trips and inspects', () => {
    const sid = randBytes(16);
    const eph = randBytes(32);
    const bytes = encodeStreamOpen(sid, eph);
    expect(inspectEnvelopeType(bytes)).toBe('stream-open');
    const hs = decodeStreamHandshake(bytes);
    expect(hs.kind).toBe('open');
    expect(hs.streamId).toEqual(sid);
    expect(hs.ephemeralPub).toEqual(eph);
  });

  test('STREAM_OPEN_ACK round-trips and inspects', () => {
    const sid = randBytes(16);
    const eph = randBytes(32);
    const bytes = encodeStreamOpenAck(sid, eph);
    expect(inspectEnvelopeType(bytes)).toBe('stream-open-ack');
    const hs = decodeStreamHandshake(bytes);
    expect(hs.kind).toBe('open-ack');
    expect(hs.streamId).toEqual(sid);
    expect(hs.ephemeralPub).toEqual(eph);
  });

  test('STREAM_FRAME carries a full ratchet message verbatim', () => {
    const sid = randBytes(16);
    const msg = makeRatchetMessage();
    const bytes = encodeStreamFrame(sid, msg);
    expect(inspectEnvelopeType(bytes)).toBe('stream-frame');
    const decoded = decodeStreamFrame(bytes);
    expect(decoded.streamId).toEqual(sid);
    expect(decoded.message.dhPublicKey).toEqual(msg.dhPublicKey);
    expect(decoded.message.previousCounter).toBe(msg.previousCounter);
    expect(decoded.message.counter).toBe(msg.counter);
    expect(decoded.message.ciphertext).toEqual(msg.ciphertext);
    expect(decoded.message.nonce).toEqual(msg.nonce);
  });

  test('rejects wrong sizes and wrong type tags', () => {
    expect(() => encodeStreamOpen(randBytes(15), randBytes(32))).toThrow();
    expect(() => encodeStreamOpen(randBytes(16), randBytes(31))).toThrow();
    expect(() => decodeStreamHandshake(encodeStreamFrame(randBytes(16), makeRatchetMessage()))).toThrow();
    expect(() => decodeStreamFrame(encodeStreamOpen(randBytes(16), randBytes(32)))).toThrow();
  });
});
