import { describe, it, expect } from 'vitest';
import { parseSseStream, type SseFrame } from './sse-parser';

/**
 * Pure-function tests for the SSE parser. Feed a hand-built
 * `ReadableStream<Uint8Array>` to verify frame splitting,
 * JSON decoding, and tolerance of CRLF / partial frames.
 */

function streamOf(chunks: ReadonlyArray<string>): ReadableStream<Uint8Array> {
  const encoder = new TextEncoder();
  return new ReadableStream<Uint8Array>({
    start(controller) {
      for (const chunk of chunks) {
        controller.enqueue(encoder.encode(chunk));
      }
      controller.close();
    },
  });
}

async function collect(stream: ReadableStream<Uint8Array>): Promise<SseFrame[]> {
  const frames: SseFrame[] = [];
  for await (const frame of parseSseStream(stream)) {
    frames.push(frame);
  }
  return frames;
}

describe('parseSseStream', () => {
  it('emits one frame per LF-separated event block', async () => {
    const frames = await collect(
      streamOf([
        'event: token\ndata: {"value":"hello"}\n\n',
        'event: token\ndata: {"value":"world"}\n\n',
        'event: done\ndata: {"stats":{}}\n\n',
      ]),
    );

    expect(frames).toEqual([
      { event: 'token', data: { value: 'hello' } },
      { event: 'token', data: { value: 'world' } },
      { event: 'done', data: { stats: {} } },
    ]);
  });

  it('handles CRLF line endings', async () => {
    const frames = await collect(streamOf(['event: token\r\ndata: {"value":"hi"}\r\n\r\n']));
    expect(frames).toEqual([{ event: 'token', data: { value: 'hi' } }]);
  });

  it('reassembles a frame split across multiple network chunks', async () => {
    const frames = await collect(
      streamOf(['event: tok', 'en\ndata: ', '{"value":"', 'split"}\n', '\n']),
    );
    expect(frames).toEqual([{ event: 'token', data: { value: 'split' } }]);
  });

  it('flushes a trailing frame that lacks the final blank line', async () => {
    const frames = await collect(streamOf(['event: done\ndata: {"stats":{"tokensIn":1}}']));
    expect(frames).toEqual([{ event: 'done', data: { stats: { tokensIn: 1 } } }]);
  });

  it('defaults the event name to "message" when only data: is present', async () => {
    const frames = await collect(streamOf(['data: {"ping":true}\n\n']));
    expect(frames).toEqual([{ event: 'message', data: { ping: true } }]);
  });

  it('returns the raw text when the data payload is not JSON', async () => {
    const frames = await collect(streamOf(['event: comment\ndata: hello world\n\n']));
    expect(frames).toEqual([{ event: 'comment', data: 'hello world' }]);
  });

  it('skips comment lines (starting with ":") per the SSE spec', async () => {
    const frames = await collect(streamOf([': keepalive\nevent: token\ndata: {"value":"x"}\n\n']));
    expect(frames).toEqual([{ event: 'token', data: { value: 'x' } }]);
  });

  it('skips frames with only an event field and no data', async () => {
    const frames = await collect(streamOf(['event: token\n\nevent: done\ndata: {}\n\n']));
    expect(frames).toEqual([{ event: 'done', data: {} }]);
  });
});
