import fs from "node:fs";
import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { resolvePreferredOpenClawTmpDir } from "../../../../src/infra/tmp-openclaw-dir.js";
import type { ResolvedAcpxPluginConfig } from "../config.js";
import { ACPX_PINNED_VERSION } from "../config.js";
import { AcpxRuntime } from "../runtime.js";

export const NOOP_LOGGER = {
  info: (_message: string) => {},
  warn: (_message: string) => {},
  error: (_message: string) => {},
  debug: (_message: string) => {},
};

const tempDirs: string[] = [];
let sharedMockCliScriptPath: Promise<string> | null = null;
let logFileSequence = 0;

const MOCK_CLI_SCRIPT = String.raw`#!/usr/bin/env node
const fs = require("node:fs");

const args = process.argv.slice(2);
const logPath = process.env.MOCK_ACPX_LOG;
const openclawShell = process.env.OPENCLAW_SHELL || "";
const writeLog = (entry) => {
  if (!logPath) return;
  fs.appendFileSync(logPath, JSON.stringify(entry) + "\n");
};
const emitJson = (payload) => process.stdout.write(JSON.stringify(payload) + "\n");
const emitUpdate = (sessionId, update) =>
  emitJson({
    jsonrpc: "2.0",
    method: "session/update",
    params: { sessionId, update },
  });

if (args.includes("--version")) {
  process.stdout.write("mock-acpx ${ACPX_PINNED_VERSION}\\n");
  process.exit(0);
}

if (args.includes("--help")) {
  process.stdout.write("mock-acpx help\\n");
  process.exit(0);
}

const commandIndex = args.findIndex(
  (arg) =>
    arg === "prompt" ||
    arg === "cancel" ||
    arg === "sessions" ||
    arg === "set-mode" ||
    arg === "set" ||
    arg === "status" ||
    arg === "config",
);
const command = commandIndex >= 0 ? args[commandIndex] : "";
const agent = commandIndex > 0 ? args[commandIndex - 1] : "unknown";

const readFlag = (flag) => {
  const idx = args.indexOf(flag);
  if (idx < 0) return "";
  return String(args[idx + 1] || "");
};

const sessionFromOption = readFlag("--session");
const ensureName = readFlag("--name");
const closeName =
  command === "sessions" && args[commandIndex + 1] === "close"
    ? String(args[commandIndex + 2] || "")
    : "";
const setModeValue = command === "set-mode" ? String(args[commandIndex + 1] || "") : "";
const setKey = command === "set" ? String(args[commandIndex + 1] || "") : "";
const setValue = command === "set" ? String(args[commandIndex + 2] || "") : "";

if (command === "sessions" && args[commandIndex + 1] === "ensure") {
  writeLog({ kind: "ensure", agent, args, sessionName: ensureName });
  if (process.env.MOCK_ACPX_ENSURE_EMPTY === "1") {
    emitJson({ action: "session_ensured", name: ensureName });
  } else {
    emitJson({
      action: "session_ensured",
      acpxRecordId: "rec-" + ensureName,
      acpxSessionId: "sid-" + ensureName,
      agentSessionId: "inner-" + ensureName,
      name: ensureName,
      created: true,
    });
  }
  process.exit(0);
}

if (command === "sessions" && args[commandIndex + 1] === "new") {
  writeLog({ kind: "new", agent, args, sessionName: ensureName });
  if (process.env.MOCK_ACPX_NEW_EMPTY === "1") {
    emitJson({ action: "session_created", name: ensureName });
  } else {
    emitJson({
      action: "session_created",
      acpxRecordId: "rec-" + ensureName,
      acpxSessionId: "sid-" + ensureName,
      agentSessionId: "inner-" + ensureName,
      name: ensureName,
      created: true,
    });
  }
  process.exit(0);
}

if (command === "config" && args[commandIndex + 1] === "show") {
  const configuredAgents = process.env.MOCK_ACPX_CONFIG_SHOW_AGENTS
    ? JSON.parse(process.env.MOCK_ACPX_CONFIG_SHOW_AGENTS)
    : {};
  emitJson({
    defaultAgent: "codex",
    defaultPermissions: "approve-reads",
    nonInteractivePermissions: "deny",
    authPolicy: "skip",
    ttl: 300,
    timeout: null,
    format: "text",
    agents: configuredAgents,
    authMethods: [],
    paths: {
      global: "/tmp/mock-global.json",
      project: "/tmp/mock-project.json",
    },
    loaded: {
      global: false,
      project: false,
    },
  });
  process.exit(0);
}

if (command === "cancel") {
  writeLog({ kind: "cancel", agent, args, sessionName: sessionFromOption });
  emitJson({
    acpxSessionId: "sid-" + sessionFromOption,
    cancelled: true,
  });
  process.exit(0);
}

if (command === "set-mode") {
  writeLog({ kind: "set-mode", agent, args, sessionName: sessionFromOption, mode: setModeValue });
  emitJson({
    action: "mode_set",
    acpxSessionId: "sid-" + sessionFromOption,
    mode: setModeValue,
  });
  process.exit(0);
}

if (command === "set") {
  writeLog({
    kind: "set",
    agent,
    args,
    sessionName: sessionFromOption,
    key: setKey,
    value: setValue,
  });
  emitJson({
    action: "config_set",
    acpxSessionId: "sid-" + sessionFromOption,
    key: setKey,
    value: setValue,
  });
  process.exit(0);
}

if (command === "status") {
  writeLog({ kind: "status", agent, args, sessionName: sessionFromOption });
  emitJson({
    acpxRecordId: sessionFromOption ? "rec-" + sessionFromOption : null,
    acpxSessionId: sessionFromOption ? "sid-" + sessionFromOption : null,
    agentSessionId: sessionFromOption ? "inner-" + sessionFromOption : null,
    status: sessionFromOption ? "alive" : "no-session",
    pid: 4242,
    uptime: 120,
  });
  process.exit(0);
}

if (command === "sessions" && args[commandIndex + 1] === "close") {
  writeLog({ kind: "close", agent, args, sessionName: closeName });
  emitJson({
    action: "session_closed",
    acpxRecordId: "rec-" + closeName,
    acpxSessionId: "sid-" + closeName,
    name: closeName,
  });
  process.exit(0);
}

if (command === "prompt") {
  const stdinText = fs.readFileSync(0, "utf8");
  writeLog({
    kind: "prompt",
    agent,
    args,
    sessionName: sessionFromOption,
    stdinText,
    openclawShell,
    openaiApiKey: process.env.OPENAI_API_KEY || "",
    githubToken: process.env.GITHUB_TOKEN || "",
  });
  const requestId = "req-1";

  emitJson({
    jsonrpc: "2.0",
    id: 0,
    method: "session/load",
    params: {
      sessionId: sessionFromOption,
      cwd: process.cwd(),
      mcpServers: [],
    },
  });
  emitJson({
    jsonrpc: "2.0",
    id: 0,
    error: {
      code: -32002,
      message: "Resource not found",
    },
  });

  emitJson({
    jsonrpc: "2.0",
    id: requestId,
    method: "session/prompt",
    params: {
      sessionId: sessionFromOption,
      prompt: [
        {
          type: "text",
          text: stdinText.trim(),
        },
      ],
    },
  });

  if (stdinText.includes("trigger-error")) {
    emitJson({
      type: "error",
      code: "-32000",
      message: "mock failure",
    });
    process.exit(1);
  }

  if (stdinText.includes("permission-denied")) {
    process.exit(5);
  }

  if (stdinText.includes("split-spacing")) {
    emitUpdate(sessionFromOption, {
      sessionUpdate: "agent_message_chunk",
      content: { type: "text", text: "alpha" },
    });
    emitUpdate(sessionFromOption, {
      sessionUpdate: "agent_message_chunk",
      content: { type: "text", text: " beta" },
    });
    emitUpdate(sessionFromOption, {
      sessionUpdate: "agent_message_chunk",
      content: { type: "text", text: " gamma" },
    });
    emitJson({ type: "done", stopReason: "end_turn" });
    process.exit(0);
  }

  if (stdinText.includes("double-done")) {
    emitUpdate(sessionFromOption, {
      sessionUpdate: "agent_message_chunk",
      content: { type: "text", text: "ok" },
    });
    emitJson({ type: "done", stopReason: "end_turn" });
    emitJson({ type: "done", stopReason: "end_turn" });
    process.exit(0);
  }

  emitUpdate(sessionFromOption, {
    sessionUpdate: "agent_thought_chunk",
    content: { type: "text", text: "thinking" },
  });
  emitUpdate(sessionFromOption, {
    sessionUpdate: "tool_call",
    toolCallId: "tool-1",
    title: "run-tests",
    status: "in_progress",
    kind: "command",
  });
  emitUpdate(sessionFromOption, {
    sessionUpdate: "agent_message_chunk",
    content: { type: "text", text: "echo:" + stdinText.trim() },
  });
  emitJson({ type: "done", stopReason: "end_turn" });
  process.exit(0);
}

writeLog({ kind: "unknown", args });
emitJson({
  type: "error",
  code: "USAGE",
  message: "unknown command",
});
process.exit(2);
`;

export async function createMockRuntimeFixture(params?: {
  permissionMode?: ResolvedAcpxPluginConfig["permissionMode"];
  queueOwnerTtlSeconds?: number;
  mcpServers?: ResolvedAcpxPluginConfig["mcpServers"];
}): Promise<{
  runtime: AcpxRuntime;
  logPath: string;
  config: ResolvedAcpxPluginConfig;
}> {
  const scriptPath = await ensureMockCliScriptPath();
  const dir = path.dirname(scriptPath);
  const logPath = path.join(dir, `calls-${logFileSequence++}.log`);
  process.env.MOCK_ACPX_LOG = logPath;

  const config: ResolvedAcpxPluginConfig = {
    command: scriptPath,
    allowPluginLocalInstall: false,
    stripProviderAuthEnvVars: false,
    installCommand: "n/a",
    cwd: dir,
    permissionMode: params?.permissionMode ?? "approve-all",
    nonInteractivePermissions: "fail",
    strictWindowsCmdWrapper: true,
    queueOwnerTtlSeconds: params?.queueOwnerTtlSeconds ?? 0.1,
    mcpServers: params?.mcpServers ?? {},
  };

  return {
    runtime: new AcpxRuntime(config, {
      queueOwnerTtlSeconds: params?.queueOwnerTtlSeconds,
      logger: NOOP_LOGGER,
    }),
    logPath,
    config,
  };
}

async function ensureMockCliScriptPath(): Promise<string> {
  if (sharedMockCliScriptPath) {
    return await sharedMockCliScriptPath;
  }
  sharedMockCliScriptPath = (async () => {
    const dir = await mkdtemp(
      path.join(resolvePreferredOpenClawTmpDir(), "openclaw-acpx-runtime-test-"),
    );
    tempDirs.push(dir);
    const scriptPath = path.join(dir, "mock-acpx.cjs");
    await writeFile(scriptPath, MOCK_CLI_SCRIPT, "utf8");
    await chmod(scriptPath, 0o755);
    return scriptPath;
  })();
  return await sharedMockCliScriptPath;
}

export async function readMockRuntimeLogEntries(
  logPath: string,
): Promise<Array<Record<string, unknown>>> {
  if (!fs.existsSync(logPath)) {
    return [];
  }
  const raw = await readFile(logPath, "utf8");
  return raw
    .split(/\r?\n/)
    .map((line) => line.trim())
    .filter(Boolean)
    .map((line) => JSON.parse(line) as Record<string, unknown>);
}

export async function cleanupMockRuntimeFixtures(): Promise<void> {
  delete process.env.MOCK_ACPX_LOG;
  delete process.env.MOCK_ACPX_CONFIG_SHOW_AGENTS;
  sharedMockCliScriptPath = null;
  logFileSequence = 0;
  while (tempDirs.length > 0) {
    const dir = tempDirs.pop();
    if (!dir) {
      continue;
    }
    await rm(dir, {
      recursive: true,
      force: true,
      maxRetries: 10,
      retryDelay: 10,
    });
  }
}
