/**
 * Tests for runScript() — focuses on the platform branch added for
 * Windows compatibility. The actual subprocess is mocked; we only
 * verify the spawn call shape (cmd + args) and the ENOENT-on-Windows
 * error message.
 *
 * We can't run the real bash on a non-Windows CI when testing the
 * win32 branch (and vice versa on Windows), so this test stubs
 * `process.platform` and `child_process.spawn` to exercise both
 * branches deterministically regardless of where vitest runs.
 */

import { EventEmitter } from 'node:events';
import * as childProcess from 'node:child_process';
import { afterEach, describe, expect, it, vi } from 'vitest';

import { runScript } from './workspace-creator.js';

vi.mock('node:child_process', () => ({
  spawn: vi.fn(),
}));

const mockSpawn = vi.mocked(childProcess.spawn);

interface FakeChild extends EventEmitter {
  stdout: EventEmitter;
  stderr: EventEmitter;
  kill: ReturnType<typeof vi.fn>;
  exitCode: number | null;
}

function makeFakeChild(): FakeChild {
  const child = new EventEmitter() as FakeChild;
  child.stdout = new EventEmitter();
  child.stderr = new EventEmitter();
  child.kill = vi.fn();
  child.exitCode = null;
  return child;
}

function setPlatform(value: NodeJS.Platform): void {
  Object.defineProperty(process, 'platform', { value, configurable: true });
}

describe('runScript platform branching', () => {
  const originalPlatform = process.platform;

  afterEach(() => {
    setPlatform(originalPlatform);
    mockSpawn.mockReset();
  });

  it('on macOS / Linux, spawns the script directly so kernel reads the shebang', async () => {
    setPlatform('darwin');
    const child = makeFakeChild();
    mockSpawn.mockReturnValue(child as unknown as childProcess.ChildProcess);

    const promise = runScript('/tmp/foo/bootstrap.sh', ['tag-1', '/out'], { FOO: 'bar' }, 60_000);
    child.emit('close', 0);
    const res = await promise;

    expect(res.ok).toBe(true);
    expect(mockSpawn).toHaveBeenCalledTimes(1);
    expect(mockSpawn).toHaveBeenCalledWith(
      '/tmp/foo/bootstrap.sh',
      ['tag-1', '/out'],
      expect.objectContaining({
        env: expect.objectContaining({ FOO: 'bar' }),
        stdio: ['ignore', 'pipe', 'pipe'],
      }),
    );
  });

  it('on win32, wraps bash with the script as first arg (kernel does not read shebang)', async () => {
    setPlatform('win32');
    const child = makeFakeChild();
    mockSpawn.mockReturnValue(child as unknown as childProcess.ChildProcess);

    const promise = runScript(
      'C:\\Users\\me\\templates\\chat\\bootstrap.sh',
      ['tag-1', 'C:\\out'],
      {},
      60_000,
    );
    child.emit('close', 0);
    const res = await promise;

    expect(res.ok).toBe(true);
    expect(mockSpawn).toHaveBeenCalledWith(
      'bash',
      ['C:\\Users\\me\\templates\\chat\\bootstrap.sh', 'tag-1', 'C:\\out'],
      expect.any(Object),
    );
  });

  it('on win32, ENOENT spawn error surfaces a Git-for-Windows install hint', async () => {
    setPlatform('win32');
    const child = makeFakeChild();
    mockSpawn.mockReturnValue(child as unknown as childProcess.ChildProcess);

    const promise = runScript('C:\\bootstrap.sh', [], {}, 60_000);
    child.emit('error', new Error('spawn bash ENOENT'));
    const res = await promise;

    expect(res.ok).toBe(false);
    expect(res.stderr).toMatch(/spawn bash ENOENT/);
    expect(res.stderr).toMatch(/gitforwindows\.org/);
    expect(res.stderr).toMatch(/WSL2/);
  });

  it('on macOS / Linux, ENOENT does NOT add the Windows hint', async () => {
    setPlatform('darwin');
    const child = makeFakeChild();
    mockSpawn.mockReturnValue(child as unknown as childProcess.ChildProcess);

    const promise = runScript('/tmp/missing.sh', [], {}, 60_000);
    child.emit('error', new Error('spawn /tmp/missing.sh ENOENT'));
    const res = await promise;

    expect(res.ok).toBe(false);
    expect(res.stderr).not.toMatch(/gitforwindows\.org/);
  });
});
