import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

// ─── Sandbox lifecycle tests (mock docker spawn, test orchestration) ─────────

describe('sandbox runner lifecycle', () => {
  beforeEach(() => {
    vi.restoreAllMocks();
  });

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

  it('runCodeEval is importable', async () => {
    const mod = await import('../sandbox-runner.js');
    expect(typeof mod.runCodeEval).toBe('function');
  });

  it('bounded fan-out via Promise.allSettled', async () => {
    // Test the bounded concurrency pattern directly.
    const tasks = Array.from({ length: 10 }, (_, i) => ({ id: `task_${i}` }));
    const concurrency = 4;
    const executionOrder: number[] = [];
    const activeCount: number[] = [];
    let currentlyActive = 0;

    const results = await Promise.allSettled(
      tasks.slice(0, concurrency).map(async (task, idx) => {
        currentlyActive++;
        activeCount.push(currentlyActive);
        await new Promise((r) => setTimeout(r, 10 + idx * 5));
        executionOrder.push(idx);
        currentlyActive--;
        return { taskId: task.id, idx };
      }),
    );

    // All should fulfill.
    expect(results.filter((r) => r.status === 'fulfilled').length).toBe(concurrency);
    // Max concurrent should not exceed concurrency limit.
    expect(Math.max(...activeCount)).toBeLessThanOrEqual(concurrency);
  });

  it('per-task finally cleanup runs on error', async () => {
    const cleanupCalls: string[] = [];

    const tasks = [
      { id: 'task_ok' },
      { id: 'task_fail' },
      { id: 'task_ok2' },
    ];

    const results = await Promise.allSettled(
      tasks.map(async (task) => {
        try {
          if (task.id === 'task_fail') {
            throw new Error('simulated failure');
          }
          return { ok: true };
        } finally {
          cleanupCalls.push(task.id);
        }
      }),
    );

    // All cleanup calls should run, even for the failed task.
    expect(cleanupCalls).toContain('task_ok');
    expect(cleanupCalls).toContain('task_fail');
    expect(cleanupCalls).toContain('task_ok2');

    // One rejection, two fulfillments.
    expect(results.filter((r) => r.status === 'fulfilled').length).toBe(2);
    expect(results.filter((r) => r.status === 'rejected').length).toBe(1);
  });

  it('kill-on-timeout pattern', async () => {
    // Test that spawn with timeout + SIGKILL works.
    const { spawn } = await import('node:child_process');
    const child = spawn('sleep', ['300']);
    const timeoutHandle = setTimeout(() => {
      child.kill('SIGKILL');
    }, 100);

    await new Promise<void>((resolve) => {
      child.on('close', () => {
        clearTimeout(timeoutHandle);
        resolve();
      });
    });

    // SIGKILL gives signal, not exit code.
    expect(child.killed).toBe(true);
  });

  it('allSettled isolation: one failure does not abort others', async () => {
    const completed: string[] = [];

    const results = await Promise.allSettled([
      (async () => {
        await new Promise((r) => setTimeout(r, 50));
        completed.push('task1');
        return 'ok1';
      })(),
      (async () => {
        await new Promise((r) => setTimeout(r, 20));
        throw new Error('fail');
      })(),
      (async () => {
        await new Promise((r) => setTimeout(r, 50));
        completed.push('task3');
        return 'ok3';
      })(),
    ]);

    // Both successful tasks completed despite the failure.
    expect(completed).toContain('task1');
    expect(completed).toContain('task3');

    expect(results[0].status).toBe('fulfilled');
    expect(results[1].status).toBe('rejected');
    expect(results[2].status).toBe('fulfilled');
  });

  it('pruneOrphanContainers handles missing docker gracefully', async () => {
    // The pruneOrphanContainers function is internal but handles docker errors gracefully.
    // We verify the module loads without error even if docker is not available.
    const mod = await import('../sandbox-runner.js');
    expect(typeof mod.runCodeEval).toBe('function');
  });
});
