import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';

import { spawn, resolveShellConfig } from '../src/comm/child_process.js';

describe('process spawn options', () => {
  const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform');
  const originalEnv = process.env;
  const tempDirs: string[] = [];

  afterEach(() => {
    jest.clearAllMocks();
    process.env = originalEnv;
    if (originalPlatform) {
      Object.defineProperty(process, 'platform', originalPlatform);
    }
    tempDirs.splice(0).forEach(dir => fs.rmSync(dir, { recursive: true, force: true }));
  });

  it('spawn automatically injects windowsHide: true', () => {
    const spawnMock = jest.fn().mockReturnValue(createFakeChild());

    jest.resetModules();
    jest.doMock('child_process', () => ({
      ...jest.requireActual('child_process'),
      spawn: spawnMock,
    }));

    let wrappedSpawn: typeof spawn;
    jest.isolateModules(() => {
      ({ spawn: wrappedSpawn } = require('../src/comm/child_process.js'));
    });

    wrappedSpawn!('echo', ['hello'], { stdio: 'ignore' });

    expect(spawnMock).toHaveBeenCalledWith(
      'echo',
      ['hello'],
      expect.objectContaining({ stdio: 'ignore', windowsHide: true })
    );
  });

  it('uses Git Bash before cmd.exe on Windows when available', () => {
    Object.defineProperty(process, 'platform', { value: 'win32' });
    const root = fs.mkdtempSync(path.join(os.tmpdir(), 'neta-git-bash-'));
    tempDirs.push(root);
    const gitBash = path.join(root, 'Git', 'bin', 'bash.exe');
    fs.mkdirSync(path.dirname(gitBash), { recursive: true });
    fs.writeFileSync(gitBash, '', 'utf8');
    process.env = {
      ...originalEnv,
      ProgramFiles: root,
      'ProgramFiles(x86)': '',
      ComSpec: 'C:\\Windows\\System32\\cmd.exe',
    };

    const config = resolveShellConfig();

    expect(config).toEqual({
      shell: gitBash,
      args: ['-lc'],
    });
  });

  it('hides subagent worker process windows', async () => {
    const fakeChild = createFakeChild();
    const spawnMock = jest.fn().mockReturnValue(fakeChild);

    jest.resetModules();
    jest.doMock('child_process', () => ({
      spawn: spawnMock,
    }));

    let runner: any;
    let protocolVersion: number;
    await jest.isolateModulesAsync(async () => {
      ({ SubagentProcessRunner: runner } = await import('../src/modules/netaclaw/subagent/process_runner.js'));
      ({ SUBAGENT_WORKER_PROTOCOL_VERSION: protocolVersion } = await import('../src/modules/netaclaw/subagent/process_protocol.js'));
    });

    const instance = new runner({ workerPath: 'worker.js' });
    void instance.run({
      protocolVersion,
      runId: 'run-hidden',
      sessionId: 'session-1',
      parentMessageId: 1,
      parentToolCallId: 'tool-1',
      parentAgentId: 1,
      task: { goal: 'test' },
      agentConfig: { name: 'agent', systemPrompt: 'test', model: 'openai:gpt-5.4', apiKey: 'test-key' },
      toolNames: [],
    }).catch(() => undefined);

    expect(spawnMock).toHaveBeenCalledWith(
      expect.any(String),
      expect.any(Array),
      expect.objectContaining({ windowsHide: true })
    );
  });
});

function createFakeChild() {
  const { PassThrough } = require('stream');
  const { EventEmitter } = require('events');
  const child = new EventEmitter();
  child.stdin = new PassThrough();
  child.stdout = new PassThrough();
  child.stderr = new PassThrough();
  child.killed = false;
  child.kill = jest.fn(() => {
    child.killed = true;
    child.emit('close', 0);
    return true;
  });
  return child;
}
