import { execFile } from 'child_process';

export interface DockerSandboxConfig {
  sessionId: string;
  image: string;
  workspaceDir: string;
  network: 'none' | 'bridge' | 'host';
  memoryLimit: string;
  cpuLimit: string;
  timeoutSeconds: number;
}

export interface ExecOptions {
  cwd?: string;
  timeout?: number;
}

export interface ExecResult {
  stdout: string;
  stderr: string;
}

/**
 * Manages a single Docker container for sandboxed tool execution.
 * Uses the Docker CLI directly (no SDK dependency).
 */
export class DockerSandbox {
  private config: DockerSandboxConfig;
  private _containerId: string | null = null;
  private _hostWorkdir: string;

  constructor(config: DockerSandboxConfig) {
    this.config = config;
    const sanitizedId = config.sessionId.replace(/[^a-zA-Z0-9_-]/g, '_');
    this._hostWorkdir = `/tmp/flynn-sandbox-${sanitizedId}`;
  }

  get containerId(): string | null {
    return this._containerId;
  }

  get containerName(): string {
    const sanitizedId = this.config.sessionId.replace(/[^a-zA-Z0-9_-]/g, '_');
    return `flynn-${sanitizedId}`;
  }

  /** Create and start the sandbox container. */
  async create(): Promise<void> {
    const args = [
      'create',
      '--name', this.containerName,
      '--memory', this.config.memoryLimit,
      '--cpus', this.config.cpuLimit,
      '--network', this.config.network,
      '-v', `${this._hostWorkdir}:${this.config.workspaceDir}`,
      this.config.image,
      'sleep', 'infinity',
    ];

    const createResult = await this.dockerCmd(args);
    this._containerId = createResult.stdout.trim();

    await this.dockerCmd(['start', this._containerId]);
  }

  /** Execute a command inside the container. */
  async exec(command: string, opts?: ExecOptions): Promise<ExecResult> {
    if (!this._containerId) {
      throw new Error('Sandbox container not created. Call create() first.');
    }

    const args = ['exec'];
    if (opts?.cwd) {
      args.push('-w', opts.cwd);
    }
    args.push(this._containerId, 'bash', '-c', command);

    const timeout = opts?.timeout ?? this.config.timeoutSeconds * 1000;
    return this.dockerCmd(args, timeout);
  }

  /** Force-remove the container. */
  async destroy(): Promise<void> {
    if (!this._containerId) {return;}

    try {
      await this.dockerCmd(['rm', '-f', this._containerId]);
    } catch {
      // Ignore errors during cleanup
    }
    this._containerId = null;
  }

  /** Check if Docker is available on this host. */
  static async isAvailable(): Promise<boolean> {
    try {
      await new Promise<string>((resolve, reject) => {
        execFile('docker', ['version', '--format', '{{.Server.Version}}'], {
          timeout: 5000,
        }, (error, stdout) => {
          if (error) {reject(error);}
          else {resolve(stdout);}
        });
      });
      return true;
    } catch {
      return false;
    }
  }

  /** Run a docker CLI command. */
  private dockerCmd(args: string[], timeout = 30_000): Promise<ExecResult> {
    return new Promise((resolve, reject) => {
      execFile('docker', args, { timeout, maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => {
        if (error) {
          reject(error);
          return;
        }
        resolve({ stdout, stderr });
      });
    });
  }
}
