import { describe, it, expect, vi, beforeEach } from 'vitest';
import { SandboxManager } from './manager.js';
import { DockerSandbox } from './docker.js';
import type { SandboxConfig } from '../config/schema.js';

// Mock DockerSandbox
vi.mock('./docker.js', () => ({
  DockerSandbox: vi.fn().mockImplementation(() => ({
    create: vi.fn().mockResolvedValue(undefined),
    destroy: vi.fn().mockResolvedValue(undefined),
    exec: vi.fn().mockResolvedValue({ stdout: '', stderr: '' }),
    containerId: 'mock-container',
  })),
}));

describe('SandboxManager', () => {
  const defaultConfig: SandboxConfig = {
    enabled: true,
    image: 'node:22-slim',
    workspace_dir: '/workspace',
    network: 'none',
    memory_limit: '512m',
    cpu_limit: '1.0',
    timeout_seconds: 300,
  };

  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('getOrCreate()', () => {
    it('creates a new sandbox for unknown session', async () => {
      const manager = new SandboxManager(defaultConfig);
      const sandbox = await manager.getOrCreate('session-1');

      expect(DockerSandbox).toHaveBeenCalledWith(expect.objectContaining({
        sessionId: 'session-1',
        image: 'node:22-slim',
      }));
      expect(sandbox.create).toHaveBeenCalled();
    });

    it('reuses existing sandbox for same session', async () => {
      const manager = new SandboxManager(defaultConfig);
      const first = await manager.getOrCreate('session-1');
      const second = await manager.getOrCreate('session-1');

      expect(first).toBe(second);
      expect(DockerSandbox).toHaveBeenCalledTimes(1);
    });

    it('creates separate sandboxes for different sessions', async () => {
      const manager = new SandboxManager(defaultConfig);
      await manager.getOrCreate('session-1');
      await manager.getOrCreate('session-2');

      expect(DockerSandbox).toHaveBeenCalledTimes(2);
    });
  });

  describe('destroy()', () => {
    it('destroys sandbox and removes from cache', async () => {
      const manager = new SandboxManager(defaultConfig);
      const sandbox = await manager.getOrCreate('session-1');

      await manager.destroy('session-1');
      expect(sandbox.destroy).toHaveBeenCalled();

      // Should create a new one now
      await manager.getOrCreate('session-1');
      expect(DockerSandbox).toHaveBeenCalledTimes(2);
    });

    it('does nothing for unknown session', async () => {
      const manager = new SandboxManager(defaultConfig);
      await manager.destroy('nonexistent'); // should not throw
    });
  });

  describe('destroyAll()', () => {
    it('destroys all sandboxes', async () => {
      const manager = new SandboxManager(defaultConfig);
      const s1 = await manager.getOrCreate('session-1');
      const s2 = await manager.getOrCreate('session-2');

      await manager.destroyAll();
      expect(s1.destroy).toHaveBeenCalled();
      expect(s2.destroy).toHaveBeenCalled();
    });
  });
});
