import { describe, it, expect, vi } from 'vitest';
import { startSSE, sendSSEData, sendSSEEvent, endSSE } from './per-request.js';
import type { FastifyReply } from 'fastify';

function mockReply(): FastifyReply {
  const chunks: string[] = [];
  return {
    raw: {
      writeHead: vi.fn(),
      write: vi.fn((chunk: string) => {
        chunks.push(chunk);
        return true;
      }),
      end: vi.fn(),
      _chunks: chunks,
    },
    hijack: vi.fn(),
  } as unknown as FastifyReply;
}

describe('per-request SSE helpers', () => {
  describe('startSSE', () => {
    it('sets correct SSE headers', () => {
      const reply = mockReply();
      startSSE(reply);
      expect(reply.raw.writeHead).toHaveBeenCalledWith(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        Connection: 'keep-alive',
        'X-Accel-Buffering': 'no',
      });
    });

    it('hijacks the reply', () => {
      const reply = mockReply();
      startSSE(reply);
      expect(reply.hijack).toHaveBeenCalled();
    });
  });

  describe('sendSSEData', () => {
    it('formats object data as JSON', () => {
      const reply = mockReply();
      sendSSEData(reply, { hello: true });
      expect(reply.raw.write).toHaveBeenCalledWith('data: {"hello":true}\n\n');
    });

    it('formats string data without double-encoding', () => {
      const reply = mockReply();
      sendSSEData(reply, 'plain text');
      expect(reply.raw.write).toHaveBeenCalledWith('data: plain text\n\n');
    });

    it('formats number data as JSON', () => {
      const reply = mockReply();
      sendSSEData(reply, 42);
      expect(reply.raw.write).toHaveBeenCalledWith('data: 42\n\n');
    });
  });

  describe('sendSSEEvent', () => {
    it('formats named event with object data', () => {
      const reply = mockReply();
      sendSSEEvent(reply, 'token', { text: 'hi' });
      expect(reply.raw.write).toHaveBeenCalledWith('event: token\ndata: {"text":"hi"}\n\n');
    });

    it('formats named event with string data', () => {
      const reply = mockReply();
      sendSSEEvent(reply, 'status', 'done');
      expect(reply.raw.write).toHaveBeenCalledWith('event: status\ndata: done\n\n');
    });
  });

  describe('endSSE', () => {
    it('sends the [DONE] sentinel', () => {
      const reply = mockReply();
      endSSE(reply);
      expect(reply.raw.write).toHaveBeenCalledWith('data: [DONE]\n\n');
    });

    it('ends the stream', () => {
      const reply = mockReply();
      endSSE(reply);
      expect(reply.raw.end).toHaveBeenCalled();
    });
  });
});
