/**
 * harness-gateway.test.ts — Unit tests for createHarnessGateway (Task 5).
 *
 * Tests:
 *  - rawUrl carries __appToken and space query params
 *  - writeFile is present (not read-only)
 *  - deleteFile is present
 *  - fetchContent calls correct endpoint with token
 *  - listFiles calls correct endpoint with token
 */
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { createHarnessGateway } from './harness-gateway';

const SPACE_ID = 'space-abc';
const TOKEN = 'tok-xyz';

// Minimal fetch mock
let fetchMock: ReturnType<typeof vi.fn>;

beforeEach(() => {
  fetchMock = vi.fn();
  vi.stubGlobal('fetch', fetchMock);
});

afterEach(() => {
  vi.unstubAllGlobals();
});

describe('createHarnessGateway', () => {
  it('rawUrl carries __appToken and space in query params', () => {
    const gw = createHarnessGateway(SPACE_ID, TOKEN);
    const url = gw.rawUrl('apps/myapp/index.html');
    expect(url).toContain('__appToken=tok-xyz');
    expect(url).toContain(`/spaces/${SPACE_ID}/files/raw`);
    expect(url).toContain('path=');
  });

  it('writeFile is defined (gateway is not read-only)', () => {
    const gw = createHarnessGateway(SPACE_ID, TOKEN);
    expect(typeof gw.writeFile).toBe('function');
  });

  it('deleteFile is defined (gateway is not read-only)', () => {
    const gw = createHarnessGateway(SPACE_ID, TOKEN);
    expect(typeof gw.deleteFile).toBe('function');
  });

  it('fetchContent calls /files/content?path= with x-app-token header', async () => {
    fetchMock.mockResolvedValueOnce({ ok: true, text: async () => 'hello' });
    const gw = createHarnessGateway(SPACE_ID, TOKEN);
    const result = await gw.fetchContent('apps/myapp/index.html');
    expect(result).toBe('hello');
    const [url, opts] = fetchMock.mock.calls[0];
    expect(url).toContain(`/spaces/${SPACE_ID}/files/content`);
    expect(url).toContain('path=');
    expect((opts as RequestInit).headers).toMatchObject({ 'x-app-token': TOKEN });
  });

  it('listFiles calls /files?path= with x-app-token header', async () => {
    fetchMock.mockResolvedValueOnce({
      ok: true,
      json: async () => ({ entries: [], basePath: '', path: '' }),
    });
    const gw = createHarnessGateway(SPACE_ID, TOKEN);
    const result = await gw.listFiles('apps/myapp');
    expect(result).toEqual({ entries: [], basePath: '', path: '' });
    const [url, opts] = fetchMock.mock.calls[0];
    expect(url).toContain(`/spaces/${SPACE_ID}/files`);
    expect((opts as RequestInit).headers).toMatchObject({ 'x-app-token': TOKEN });
  });

  it('writeFile sends POST to /files/write with x-app-token header', async () => {
    fetchMock.mockResolvedValueOnce({
      ok: true,
      json: async () => ({ path: 'apps/myapp/out.txt', bytes: 5 }),
    });
    const gw = createHarnessGateway(SPACE_ID, TOKEN);
    const result = await gw.writeFile!('apps/myapp/out.txt', 'hello');
    expect(result).toEqual({ ok: true });
    const [url, opts] = fetchMock.mock.calls[0];
    expect(url).toContain(`/spaces/${SPACE_ID}/files/write`);
    expect(url).toContain(`__appToken=${TOKEN}`);
    expect((opts as RequestInit).headers).toMatchObject({ 'x-app-token': TOKEN });
  });

  it('deleteFile sends POST to /files/delete with x-app-token header', async () => {
    fetchMock.mockResolvedValueOnce({
      ok: true,
      json: async () => ({ deleted: ['apps/myapp/out.txt'], skipped: [] }),
    });
    const gw = createHarnessGateway(SPACE_ID, TOKEN);
    const result = await gw.deleteFile!('apps/myapp/out.txt');
    expect(result).toEqual({ ok: true });
    const [url, opts] = fetchMock.mock.calls[0];
    expect(url).toContain(`/spaces/${SPACE_ID}/files/delete`);
    expect(url).toContain(`__appToken=${TOKEN}`);
    expect((opts as RequestInit).headers).toMatchObject({ 'x-app-token': TOKEN });
  });
});
