/**
 * Orb Runtime Proof Fixture
 *
 * Opt-in integration proof that exercises the Orb lifecycle in distinct,
 * independently reported stages, each with a stable marker:
 *
 *   STAGE 1  Docker sandbox lifecycle      ORB_STAGE_DOCKER_OK / _BLOCKED
 *   STAGE 2  AgentOS VM + OpenCode session  ORB_STAGE_AGENTOS_OK / _BLOCKED
 *   STAGE 3  Model turn via the gateway     ORB_STAGE_MODEL_TURN_OK / _BLOCKED
 *
 * A missing or unreachable model gateway is BLOCKED, never passed. The proof
 * only prints ORB_PROOF_PASSED when the model turn completes. It never prints
 * gateway secrets; only provider/model identifiers and reachability labels.
 *
 * Gateway credentials resolve from ORB_GATEWAY_* with safe fallbacks to the
 * shared AGENT_MODEL_* variables. Container removal is always verified after
 * disposal.
 *
 * Usage:
 *   ORB_PROOF=1 \
 *   ORB_GATEWAY_API_KEY=... \
 *   ORB_GATEWAY_BASE_URL=https://ai.example.com/v1 \
 *   ORB_GATEWAY_MODEL=glm-5.2 \
 *   ORB_GATEWAY_PROVIDER=cheaptricks \
 *   bun run scripts/orb-proof.ts
 */
/* eslint-disable no-console -- proof fixture is a CLI tool */
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";

import { Effect } from "effect";

import { DockerSandboxProvider } from "../packages/agents/src/orb/docker-sandbox";
import type { DockerSandboxOptions } from "../packages/agents/src/orb/docker-sandbox";
import type { OrbEvent } from "../packages/agents/src/orb/events";
import { OrbRuntime } from "../packages/agents/src/orb/runtime";
import type { OrbHandle } from "../packages/agents/src/orb/runtime";

const MARKER = {
  blocked: "ORB_PROOF_BLOCKED",
  failed: "ORB_PROOF_FAILED",
  passed: "ORB_PROOF_PASSED",
} as const;

const STAGE = {
  agentosBlocked: "ORB_STAGE_AGENTOS_BLOCKED",
  agentosOk: "ORB_STAGE_AGENTOS_OK",
  dockerBlocked: "ORB_STAGE_DOCKER_BLOCKED",
  dockerOk: "ORB_STAGE_DOCKER_OK",
  modelBlocked: "ORB_STAGE_MODEL_TURN_BLOCKED",
  modelOk: "ORB_STAGE_MODEL_TURN_OK",
} as const;

interface Gateway {
  apiKey: string;
  baseUrl: string;
  model: string;
  provider: string;
}

const envValue = (key: string): string | undefined => process.env[key];

const resolveGateway = (): Gateway | null => {
  const apiKey =
    envValue("ORB_GATEWAY_API_KEY") ?? envValue("AGENT_MODEL_API_KEY");
  const baseUrl =
    envValue("ORB_GATEWAY_BASE_URL") ?? envValue("AGENT_MODEL_BASE_URL");
  const model = envValue("ORB_GATEWAY_MODEL") ?? envValue("AGENT_MODEL_NAME");
  const provider =
    envValue("ORB_GATEWAY_PROVIDER") ?? envValue("AGENT_MODEL_PROVIDER");
  if (
    !apiKey?.trim() ||
    !baseUrl?.trim() ||
    !model?.trim() ||
    !provider?.trim()
  ) {
    return null;
  }
  return { apiKey, baseUrl, model, provider };
};

const runCli = async (args: readonly string[]): Promise<string> => {
  try {
    const proc = Bun.spawn([...args], { stderr: "pipe", stdout: "pipe" });
    const stdout = await new Response(proc.stdout).text();
    await proc.exited;
    return stdout;
  } catch {
    return "";
  }
};

const dockerVersion = async (): Promise<string | null> => {
  const out = await runCli([
    "docker",
    "version",
    "--format",
    "{{.Server.Version}}",
  ]);
  const trimmed = out.trim();
  return trimmed.length > 0 ? trimmed : null;
};

const containerExists = async (name: string): Promise<boolean> => {
  const listing = await runCli([
    "docker",
    "ps",
    "-a",
    "--filter",
    `name=^${name}$`,
    "--format",
    "{{.ID}}",
  ]);
  return listing.trim().length > 0;
};

/** Verify a container is gone after disposal; returns true when removed. */
const verifyRemoved = async (name: string): Promise<boolean> => {
  const present = await containerExists(name);
  console.log(
    `    [verify] container ${name} ${present ? "STILL PRESENT" : "removed"}`
  );
  return !present;
};

const TINY_PROJECT: Record<string, string> = {
  "index.test.ts": `import { add } from "./index";\nimport { expect, test } from "bun:test";\n\ntest("add", () => {\n  expect(add(1, 2)).toBe(3);\n});\n`,
  "index.ts": `export function add(a: number, b: number): number {\n  return a + b;\n}\n`,
  "package.json": `{"name":"tiny","scripts":{"test":"bun test"}}\n`,
};

const prepareProject = async (hostWorkspace: string): Promise<void> => {
  await mkdir(path.join(hostWorkspace, "repository"), { recursive: true });
  for (const [file, content] of Object.entries(TINY_PROJECT)) {
    // eslint-disable-next-line no-await-in-loop -- sequential file writes are intentional
    await writeFile(path.join(hostWorkspace, "repository", file), content);
  }
};

// ---------------------------------------------------------------------------
// STAGE 1 — Docker sandbox lifecycle (standalone, no sidecar required)
// ---------------------------------------------------------------------------

const stageDocker = async (image: string | undefined): Promise<boolean> => {
  const version = await dockerVersion();
  if (!version) {
    console.log("[docker] Docker daemon not available");
    console.log(STAGE.dockerBlocked);
    return false;
  }
  console.log(`[docker] server version ${version}`);

  const container = `orb-proof-docker-${Date.now()}`;
  const hostWorkspace = `/tmp/orb-proof-docker-${Date.now()}`;
  const options: DockerSandboxOptions =
    image === undefined
      ? { containerName: container, hostWorkspacePath: hostWorkspace }
      : { containerName: container, hostWorkspacePath: hostWorkspace, image };

  try {
    await prepareProject(hostWorkspace);
    const provider = await Effect.runPromise(
      DockerSandboxProvider.create(options)
    );
    const client = await provider.start();

    console.log("[docker] running bun install in sandbox...");
    const install = await client.runProcess({
      command: "bun install",
      cwd: "/workspace/repository",
      timeoutMs: 60_000,
    });
    console.log(`    bun install exit: ${install.exitCode}`);

    console.log("[docker] running bun test in sandbox...");
    const test = await client.runProcess({
      command: "bun test",
      cwd: "/workspace/repository",
      timeoutMs: 60_000,
    });
    console.log(`    bun test exit: ${test.exitCode}`);

    await provider.dispose();
    const removed = await verifyRemoved(container);
    if (install.exitCode !== 0 || test.exitCode !== 0 || !removed) {
      console.log("[docker] sandbox lifecycle did not complete cleanly");
      console.log(STAGE.dockerBlocked);
      return false;
    }
    console.log(STAGE.dockerOk);
    return true;
  } catch (error) {
    console.log(
      `[docker] stage failed: ${error instanceof Error ? error.message : String(error)}`
    );
    await runCli(["docker", "rm", "-f", container]);
    await verifyRemoved(container);
    console.log(STAGE.dockerBlocked);
    return false;
  }
};

// ---------------------------------------------------------------------------
// STAGE 2 — AgentOS VM + OpenCode session
// ---------------------------------------------------------------------------

const stageAgentOs = async (
  gateway: Gateway,
  image: string | undefined,
  events: OrbEvent[]
): Promise<{ container: string; handle: OrbHandle } | null> => {
  const container = `orb-proof-${Date.now()}`;
  const hostWorkspace = `/tmp/orb-proof-${Date.now()}`;
  const runtime = new OrbRuntime(
    image === undefined ? {} : { dockerImage: image }
  );

  const createResult = await Effect.runPromise(
    Effect.match(
      runtime.createOrb({
        context: {
          artifacts: [],
          contextFiles: [],
          issueBody: "Run bun test and report the result.",
          issueTitle: "Proof: run tests",
        },
        docker: { containerName: container, hostWorkspacePath: hostWorkspace },
        gateway,
        identity: {
          projectId: "proof",
          runId: `proof-${Date.now()}`,
          workUnitId: "proof-wu",
        },
      }),
      {
        onFailure: (error) => ({ error, ok: false as const }),
        onSuccess: (handle) => ({ handle, ok: true as const }),
      }
    )
  );

  if (!createResult.ok) {
    console.log(
      `[agentos] creation failed (${createResult.error.reason}): ${createResult.error.message}`
    );
    await verifyRemoved(container);
    console.log(STAGE.agentosBlocked);
    return null;
  }

  const { handle } = createResult;
  handle.onEvent((event) => {
    events.push(event);
    const summary = (event.text ?? event.command ?? event.type).slice(0, 160);
    console.log(`    [event] ${event.type}: ${summary}`);
  });

  try {
    const vm = handle.vmInstance;
    if (!vm) {
      throw new Error("AgentOS VM is not attached");
    }
    const agents = await vm.listAgents();
    const opencode = agents.find((a) => a.id === "opencode" && a.installed);
    if (!opencode) {
      throw new Error("OpenCode agent is not installed in the VM");
    }
    console.log("[agentos] OpenCode agent available");

    await Effect.runPromise(handle.openSession());
    console.log(`[agentos] session opened: ${handle.currentSessionId ?? "?"}`);
    console.log(STAGE.agentosOk);
    return { container, handle };
  } catch (error) {
    console.log(
      `[agentos] stage failed: ${error instanceof Error ? error.message : String(error)}`
    );
    await Effect.runPromise(handle.dispose().pipe(Effect.ignore)).catch(() => {
      // best-effort cleanup
    });
    await verifyRemoved(container);
    console.log(STAGE.agentosBlocked);
    return null;
  }
};

// ---------------------------------------------------------------------------
// STAGE 3 — Model turn through the gateway
// ---------------------------------------------------------------------------

const stageModelTurn = async (handle: OrbHandle): Promise<boolean> => {
  const vm = handle.vmInstance;
  const sessionId = handle.currentSessionId;
  if (!vm || !sessionId) {
    console.log("[model] no active session to drive a model turn");
    console.log(STAGE.modelBlocked);
    return false;
  }
  try {
    console.log("[model] sending bounded turn to gateway...");
    const result = await vm.prompt({
      content: [
        { text: "Reply with exactly: ORB_PROOF_MODEL_ECHO", type: "text" },
      ],
      sessionId,
    });
    console.log(
      `[model] turn completed (stopReason=${result.stopReason ?? "?"})`
    );
    console.log(STAGE.modelOk);
    return true;
  } catch (error) {
    // An unreachable/missing gateway surfaces here as a prompt failure.
    console.log(
      `[model] gateway turn failed: ${error instanceof Error ? error.message : String(error)}`
    );
    console.log(STAGE.modelBlocked);
    return false;
  }
};

// ---------------------------------------------------------------------------
// Orchestration
// ---------------------------------------------------------------------------

const runProof = async (): Promise<number> => {
  const image = envValue("ORB_DOCKER_IMAGE");
  const events: OrbEvent[] = [];

  // STAGE 1 — Docker sandbox.
  console.log("[stage 1] Docker sandbox lifecycle...");
  const dockerOk = await stageDocker(image);
  if (!dockerOk) {
    console.log(`\n${MARKER.blocked}`);
    return 2;
  }

  // STAGE 2 + 3 — AgentOS/OpenCode + model turn.
  const gateway = resolveGateway();
  if (!gateway) {
    console.error(
      "[gateway] missing config: set ORB_GATEWAY_* (or AGENT_MODEL_* fallbacks)."
    );
    console.log(STAGE.agentosBlocked);
    console.log(`\n${MARKER.blocked}`);
    return 2;
  }
  // Identifiers only — never the key or base URL.
  console.log(`[gateway] provider=${gateway.provider} model=${gateway.model}`);

  console.log("[stage 2] AgentOS VM + OpenCode session...");
  const orb = await stageAgentOs(gateway, image, events);
  if (!orb) {
    console.log(`\n${MARKER.blocked}`);
    return 2;
  }

  console.log("[stage 3] model turn...");
  let modelOk = false;
  try {
    modelOk = await stageModelTurn(orb.handle);
  } finally {
    console.log("[dispose] disposing Orb...");
    await Effect.runPromise(orb.handle.dispose().pipe(Effect.ignore)).catch(
      () => {
        // best-effort cleanup
      }
    );
    const removed = await verifyRemoved(orb.container);
    if (!removed) {
      console.log("[dispose] container removal could not be verified");
    }
  }

  console.log(
    `\nStreamed ${events.length} normalized events (${[...new Set(events.map((e) => e.type))].join(", ")}).`
  );

  if (modelOk) {
    console.log(`\n${MARKER.passed}`);
    return 0;
  }
  console.log(`\n${MARKER.blocked}`);
  return 2;
};

// Entry point
if (envValue("ORB_PROOF") !== "1") {
  console.error(
    "Set ORB_PROOF=1 to run the proof fixture (requires Docker + model gateway)."
  );
  console.error(MARKER.blocked);
  process.exit(2);
}

(async () => {
  try {
    const code = await runProof();
    process.exit(code);
  } catch (error) {
    console.error("[proof] unexpected failure:", error);
    console.error(MARKER.failed);
    process.exit(1);
  }
})();
