import { describe, test, expect } from 'bun:test';
import { ValidationError } from '@shade/core';
import {
  encodeStreamControl,
  parseStreamControl,
  isStreamControlMessage,
} from '../src/index.js';
import type {
  StreamInitMessage,
  StreamFinishMessage,
  StreamAbortMessage,
} from '../src/index.js';

describe('control envelope encode/parse roundtrip', () => {
  test('stream-init', () => {
    const msg: StreamInitMessage = {
      kind: 'shade.stream-init/v1',
      streamId: 'AAAAAAAAAAAAAAAAAAAAAA',
      streamSecret: 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBA',
      metadata: {
        name: 'world.zip',
        sizeBytes: 1024,
        contentType: 'application/zip',
        chunkSize: 256,
        totalChunks: 4,
        sentAt: 1730000000000,
      },
      lanes: [
        {
          laneId: 0,
          partition: { kind: 'range', startByte: 0, endByte: 512, startChunk: 0 },
        },
        {
          laneId: 1,
          partition: { kind: 'range', startByte: 512, endByte: 1024, startChunk: 0 },
        },
      ],
    };
    const json = encodeStreamControl(msg);
    expect(parseStreamControl(json)).toEqual(msg);
  });

  test('stream-finish', () => {
    const msg: StreamFinishMessage = {
      kind: 'shade.stream-finish/v1',
      streamId: 'AAAAAAAAAAAAAAAAAAAAAA',
      laneSha256: [{ laneId: 0, sha256: 'abcd', chunkCount: 1, byteCount: 256 }],
      overallSha256: 'efgh',
      finishedAt: 1730000001000,
    };
    expect(parseStreamControl(encodeStreamControl(msg))).toEqual(msg);
  });

  test('stream-abort', () => {
    const msg: StreamAbortMessage = {
      kind: 'shade.stream-abort/v1',
      streamId: 'AAAAAAAAAAAAAAAAAAAAAA',
      reason: 'sender-cancel',
      message: 'user clicked cancel',
      abortedAt: 1730000002000,
    };
    expect(parseStreamControl(encodeStreamControl(msg))).toEqual(msg);
  });

  test('rejects malformed JSON', () => {
    expect(() => parseStreamControl('not-json')).toThrow(ValidationError);
  });

  test('rejects messages without a kind field', () => {
    expect(() => parseStreamControl(JSON.stringify({ foo: 'bar' }))).toThrow(ValidationError);
  });

  test('rejects messages whose kind does not start with shade.stream-', () => {
    expect(() => parseStreamControl(JSON.stringify({ kind: 'other.kind' }))).toThrow(
      ValidationError,
    );
  });
});

describe('isStreamControlMessage', () => {
  test('returns true for valid shapes', () => {
    expect(isStreamControlMessage({ kind: 'shade.stream-init/v1' })).toBe(true);
    expect(isStreamControlMessage({ kind: 'shade.stream-finish/v1' })).toBe(true);
  });

  test('returns false for non-objects and missing kind', () => {
    expect(isStreamControlMessage(null)).toBe(false);
    expect(isStreamControlMessage(42)).toBe(false);
    expect(isStreamControlMessage({})).toBe(false);
    expect(isStreamControlMessage({ kind: 'unrelated' })).toBe(false);
  });
});
