import { describe, it, expect, afterEach } from 'vitest';
import { RingBuffer, ProcessManager } from './manager.js';

describe('RingBuffer', () => {
  it('reads empty buffer as empty string', () => {
    const buf = new RingBuffer(16);
    expect(buf.read()).toBe('');
    expect(buf.size).toBe(0);
  });

  it('writes and reads data smaller than capacity', () => {
    const buf = new RingBuffer(64);
    buf.write('hello');
    expect(buf.read()).toBe('hello');
    expect(buf.size).toBe(5);
  });

  it('accumulates multiple writes', () => {
    const buf = new RingBuffer(64);
    buf.write('hello ');
    buf.write('world');
    expect(buf.read()).toBe('hello world');
    expect(buf.size).toBe(11);
  });

  it('wraps around when full, keeping newest data', () => {
    const buf = new RingBuffer(8);
    buf.write('ABCDEFGH'); // fills exactly
    expect(buf.read()).toBe('ABCDEFGH');

    buf.write('IJ'); // overwrites A and B
    expect(buf.read()).toBe('CDEFGHIJ');
  });

  it('handles data larger than capacity', () => {
    const buf = new RingBuffer(4);
    buf.write('ABCDEFGH'); // only last 4 bytes kept
    expect(buf.read()).toBe('EFGH');
    expect(buf.size).toBe(4);
  });

  it('handles exact capacity writes', () => {
    const buf = new RingBuffer(5);
    buf.write('ABCDE');
    expect(buf.read()).toBe('ABCDE');
  });

  it('clears the buffer', () => {
    const buf = new RingBuffer(16);
    buf.write('data');
    buf.clear();
    expect(buf.read()).toBe('');
    expect(buf.size).toBe(0);
  });

  it('handles Buffer input', () => {
    const buf = new RingBuffer(64);
    buf.write(Buffer.from('bytes'));
    expect(buf.read()).toBe('bytes');
  });

  it('handles multiple wraps correctly', () => {
    const buf = new RingBuffer(8);
    // Write 6 bytes
    buf.write('ABCDEF');
    expect(buf.read()).toBe('ABCDEF');
    // Write 6 more (wraps past end)
    buf.write('GHIJKL');
    // Should have the last 8 bytes: EFGHIJKL
    expect(buf.read()).toBe('EFGHIJKL');
  });
});

describe('ProcessManager', () => {
  const managers: ProcessManager[] = [];

  function createManager(config?: { maxConcurrent?: number; maxRuntimeMinutes?: number; bufferSize?: number }) {
    const m = new ProcessManager(config);
    managers.push(m);
    return m;
  }

  afterEach(async () => {
    for (const m of managers) {
      await m.shutdown();
    }
    managers.length = 0;
  });

  it('starts a process and captures output', async () => {
    const manager = createManager();
    const proc = manager.start('echo "hello background"');
    expect(proc.id).toBe('proc_1');
    expect(proc.status).toBe('running');
    expect(proc.pid).toBeGreaterThan(0);

    // Wait for process to finish
    await new Promise(resolve => setTimeout(resolve, 500));

    expect(proc.status).toBe('exited');
    expect(proc.exitCode).toBe(0);

    const output = manager.getOutput(proc.id);
    expect(output).toContain('hello background');
  });

  it('tracks multiple processes', async () => {
    const manager = createManager();
    const p1 = manager.start('echo one');
    const p2 = manager.start('echo two');
    expect(p1.id).toBe('proc_1');
    expect(p2.id).toBe('proc_2');
    expect(manager.list()).toHaveLength(2);

    await new Promise(resolve => setTimeout(resolve, 500));

    expect(manager.getOutput(p1.id)).toContain('one');
    expect(manager.getOutput(p2.id)).toContain('two');
  });

  it('kills a running process', async () => {
    const manager = createManager();
    const proc = manager.start('sleep 60');
    expect(proc.status).toBe('running');
    expect(manager.runningCount()).toBe(1);

    const killed = manager.kill(proc.id);
    expect(killed).toBe(true);

    // Wait for exit event
    await new Promise(resolve => setTimeout(resolve, 300));
    expect(proc.status).toBe('killed');
    expect(manager.runningCount()).toBe(0);
  });

  it('enforces max concurrent limit', () => {
    const manager = createManager({ maxConcurrent: 2 });
    manager.start('sleep 60');
    manager.start('sleep 60');

    expect(() => manager.start('sleep 60')).toThrow('Maximum concurrent processes (2) reached');
  });

  it('returns undefined for unknown process ID', () => {
    const manager = createManager();
    expect(manager.get('proc_999')).toBeUndefined();
  });

  it('throws when getting output of unknown process', () => {
    const manager = createManager();
    expect(() => manager.getOutput('proc_999')).toThrow('Process proc_999 not found');
  });

  it('throws when killing unknown process', () => {
    const manager = createManager();
    expect(() => manager.kill('proc_999')).toThrow('Process proc_999 not found');
  });

  it('returns false when killing a non-running process', async () => {
    const manager = createManager();
    const proc = manager.start('echo done');
    await new Promise(resolve => setTimeout(resolve, 500));
    expect(proc.status).toBe('exited');
    expect(manager.kill(proc.id)).toBe(false);
  });

  it('captures stderr in output buffer', async () => {
    const manager = createManager();
    const proc = manager.start('echo "stderr msg" >&2');
    await new Promise(resolve => setTimeout(resolve, 500));

    const output = manager.getOutput(proc.id);
    expect(output).toContain('stderr msg');
  });

  it('shutdown kills all running processes', async () => {
    const manager = createManager();
    const p1 = manager.start('sleep 60');
    const p2 = manager.start('sleep 60');
    expect(manager.runningCount()).toBe(2);

    await manager.shutdown();

    expect(p1.status).not.toBe('running');
    expect(p2.status).not.toBe('running');
  });

  it('exposes config defaults', () => {
    const manager = createManager();
    expect(manager.config.maxConcurrent).toBe(10);
    expect(manager.config.maxRuntimeMinutes).toBe(60);
    expect(manager.config.bufferSize).toBe(65536);
  });

  it('exposes custom config', () => {
    const manager = createManager({ maxConcurrent: 5, maxRuntimeMinutes: 30, bufferSize: 1024 });
    expect(manager.config.maxConcurrent).toBe(5);
    expect(manager.config.maxRuntimeMinutes).toBe(30);
    expect(manager.config.bufferSize).toBe(1024);
  });
});

describe('Process tools', () => {
  let manager: ProcessManager;

  afterEach(async () => {
    if (manager) {await manager.shutdown();}
  });

  it('process.start tool creates and returns process info', async () => {
    const { createProcessStartTool } = await import('./start.js');
    manager = new ProcessManager();
    const tool = createProcessStartTool(manager);

    expect(tool.name).toBe('process.start');
    const result = await tool.execute({ command: 'echo test' });
    expect(result.success).toBe(true);
    expect(result.output).toContain('proc_1');
    expect(result.output).toContain('PID');

    await new Promise(resolve => setTimeout(resolve, 300));
  });

  it('process.status tool returns status info', async () => {
    const { createProcessStatusTool } = await import('./status.js');
    manager = new ProcessManager();
    const tool = createProcessStatusTool(manager);
    manager.start('sleep 60');

    const result = await tool.execute({ id: 'proc_1' });
    expect(result.success).toBe(true);
    expect(result.output).toContain('Status: running');
    expect(result.output).toContain('proc_1');
  });

  it('process.status tool returns error for unknown ID', async () => {
    const { createProcessStatusTool } = await import('./status.js');
    manager = new ProcessManager();
    const tool = createProcessStatusTool(manager);

    const result = await tool.execute({ id: 'proc_999' });
    expect(result.success).toBe(false);
    expect(result.error).toContain('not found');
  });

  it('process.output tool returns process output', async () => {
    const { createProcessOutputTool } = await import('./output.js');
    manager = new ProcessManager();
    const tool = createProcessOutputTool(manager);
    manager.start('echo "tool output"');

    await new Promise(resolve => setTimeout(resolve, 500));

    const result = await tool.execute({ id: 'proc_1' });
    expect(result.success).toBe(true);
    expect(result.output).toContain('tool output');
  });

  it('process.kill tool kills a running process', async () => {
    const { createProcessKillTool } = await import('./kill.js');
    manager = new ProcessManager();
    const tool = createProcessKillTool(manager);
    manager.start('sleep 60');

    const result = await tool.execute({ id: 'proc_1' });
    expect(result.success).toBe(true);
    expect(result.output).toContain('SIGTERM');

    await new Promise(resolve => setTimeout(resolve, 300));
  });

  it('process.list tool lists all processes', async () => {
    const { createProcessListTool } = await import('./list.js');
    manager = new ProcessManager();
    const tool = createProcessListTool(manager);

    // Empty list
    let result = await tool.execute({});
    expect(result.success).toBe(true);
    expect(result.output).toBe('No managed processes.');

    // With processes
    manager.start('sleep 60');
    manager.start('echo hi');

    await new Promise(resolve => setTimeout(resolve, 300));

    result = await tool.execute({});
    expect(result.success).toBe(true);
    expect(result.output).toContain('proc_1');
    expect(result.output).toContain('proc_2');
  });

  it('createProcessTools returns all 5 tools', async () => {
    const { createProcessTools } = await import('./index.js');
    manager = new ProcessManager();
    const tools = createProcessTools(manager);
    expect(tools).toHaveLength(5);
    const names = tools.map(t => t.name);
    expect(names).toContain('process.start');
    expect(names).toContain('process.status');
    expect(names).toContain('process.output');
    expect(names).toContain('process.kill');
    expect(names).toContain('process.list');
  });
});
