import {
  createSandboxSessionEnv,
  SandboxOperationUnsupportedError,
} from "@flue/runtime";
import type {
  FileStat,
  SandboxApi,
  SandboxFactory,
  SessionEnv,
} from "@flue/runtime";
import { AgentOs } from "@rivet-dev/agentos-core";
import type { VirtualStat } from "@rivet-dev/agentos-core";

class AgentOsSandboxApi implements SandboxApi {
  private readonly vm: AgentOs;

  constructor(vm: AgentOs) {
    this.vm = vm;
  }

  async readFile(path: string): Promise<string> {
    const bytes = await this.vm.readFile(path);
    return new TextDecoder().decode(bytes);
  }

  readFileBuffer(path: string): Promise<Uint8Array> {
    return this.vm.readFile(path);
  }

  async writeFile(path: string, content: string | Uint8Array): Promise<void> {
    await this.vm.writeFile(path, content);
  }

  async stat(path: string): Promise<FileStat> {
    const s: VirtualStat = await this.vm.stat(path);
    return {
      isDirectory: s.isDirectory,
      isFile: !s.isDirectory && !s.isSymbolicLink,
      isSymbolicLink: s.isSymbolicLink,
      mtime: new Date(s.mtimeMs),
      size: s.size,
    };
  }

  readdir(path: string): Promise<string[]> {
    return this.vm.readdir(path);
  }

  async exists(path: string): Promise<boolean> {
    try {
      return await this.vm.exists(path);
    } catch {
      return false;
    }
  }

  async mkdir(path: string, options?: { recursive?: boolean }): Promise<void> {
    await this.vm.mkdir(path, options);
  }

  async rm(
    path: string,
    options?: { recursive?: boolean; force?: boolean }
  ): Promise<void> {
    // agentOS remove has no `force` flag. Reject it per the adapter contract:
    // never ignore an option or leave its behavior provider-defined.
    if (options?.force) {
      throw new SandboxOperationUnsupportedError({
        operation: "rm",
        options: ["force"],
        provider: "agentos",
      });
    }
    await this.vm.remove(path, { recursive: options?.recursive });
  }

  async exec(
    command: string,
    options?: {
      cwd?: string;
      env?: Record<string, string>;
      timeoutMs?: number;
      signal?: AbortSignal;
    }
  ): Promise<{ stdout: string; stderr: string; exitCode: number }> {
    // agentOS exec takes timeout in ms, same unit as Flue's timeoutMs.
    // Forward directly — no rounding needed.
    const result = await this.vm.exec(command, {
      cwd: options?.cwd,
      env: options?.env,
      timeout: options?.timeoutMs,
    });
    return {
      exitCode: result.exitCode,
      stderr: result.stderr,
      stdout: result.stdout,
    };
  }
}

/**
 * Adapts an agentOS VM into Flue's sandbox contract.
 *
 * The VM boots lazily on the first operation and sleeps when idle. Files
 * persist for the VM's lifetime. Shell commands run in an isolated Wasm+V8
 * Linux environment with sh and coreutils.
 */
export const agentos = (): SandboxFactory => {
  let vm: AgentOs | null = null;

  return {
    async createSessionEnv(): Promise<SessionEnv> {
      // Boot once per harness. Repeated calls reuse the same VM.
      if (!vm) {
        vm = await AgentOs.create();
      }
      const api = new AgentOsSandboxApi(vm);
      return createSandboxSessionEnv(api, "/workspace");
    },
  };
};
