// Real CLI processes must keep unrelated plugin runtimes cold on read-only command paths.
import { execFile } from "node:child_process";
import fs from "node:fs";
import { createServer, type Server } from "node:http";
import os from "node:os";
import path from "node:path";
import { promisify } from "node:util";
import { afterAll, beforeAll, expect, it } from "vitest";
import {
  createColdPluginFixture,
  isColdPluginRuntimeLoaded,
} from "../plugins/test-helpers/cold-plugin-fixtures.js";

const execFileAsync = promisify(execFile);
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-cold-commands-"));
let clawHubServer: Server;
let clawHubUrl: string;

beforeAll(async () => {
  clawHubServer = createServer((_request, response) => {
    response.writeHead(200, { "content-type": "application/json" });
    response.end('{"results":[]}');
  });
  await new Promise<void>((resolve) => {
    clawHubServer.listen(0, "127.0.0.1", resolve);
  });
  const address = clawHubServer.address();
  if (!address || typeof address === "string") {
    throw new Error("failed to bind the ClawHub fixture server");
  }
  clawHubUrl = `http://127.0.0.1:${address.port}`;
});

afterAll(async () => {
  await new Promise<void>((resolve, reject) => {
    clawHubServer.close((error) => (error ? reject(error) : resolve()));
  });
  fs.rmSync(tempRoot, { recursive: true, force: true });
});

const cases = [
  { label: "hooks", args: ["hooks", "--json"], needsMemoryOwner: false },
  {
    label: "skills-search",
    args: ["skills", "search", "fixture", "--json"],
    needsMemoryOwner: false,
  },
  {
    label: "skills-info",
    args: ["skills", "info", "fixture-skill", "--json"],
    needsMemoryOwner: false,
  },
  {
    label: "memory-status",
    args: ["memory", "status", "--agent", "main", "--json"],
    needsMemoryOwner: true,
  },
  {
    label: "memory-search",
    args: ["memory", "search", "no-hit", "--agent", "main", "--json"],
    needsMemoryOwner: true,
  },
] as const;

it.each(cases)(
  "keeps an unrelated plugin runtime cold for $label",
  async ({ args, label, needsMemoryOwner }) => {
    const root = path.join(tempRoot, label);
    const stateDir = path.join(root, "state");
    const workspaceDir = path.join(root, "workspace");
    const pluginDir = path.join(root, "cold-plugin");
    const memoryOwnerDir = path.join(root, "memory-owner");
    fs.mkdirSync(path.join(workspaceDir, "skills", "fixture-skill"), { recursive: true });
    fs.mkdirSync(pluginDir, { recursive: true });
    fs.writeFileSync(
      path.join(workspaceDir, "skills", "fixture-skill", "SKILL.md"),
      "---\nname: fixture-skill\ndescription: Cold path fixture\n---\n",
    );
    const fixture = createColdPluginFixture({
      rootDir: pluginDir,
      pluginId: `cold-${label}`,
      runtimeMessage: `${label} imported an unrelated plugin runtime`,
      manifest: { providers: [], channels: [], providerAuthChoices: [], channelConfigs: {} },
    });
    if (needsMemoryOwner) {
      // Keep this fixture about the ownership boundary: the selected CLI owner must
      // execute while the unrelated sentinel stays cold. memory-core behavior has
      // its own focused CLI suite, including the stale-search result contract.
      fs.mkdirSync(memoryOwnerDir, { recursive: true });
      fs.writeFileSync(
        path.join(memoryOwnerDir, "package.json"),
        JSON.stringify({
          name: "@example/openclaw-memory-owner",
          version: "1.0.0",
          type: "commonjs",
          openclaw: { extensions: ["./index.cjs"] },
        }),
      );
      fs.writeFileSync(
        path.join(memoryOwnerDir, "openclaw.plugin.json"),
        JSON.stringify({
          id: "memory-owner",
          name: "Memory Owner",
          configSchema: { type: "object", additionalProperties: false, properties: {} },
          commandAliases: [{ name: "fixture-memory", kind: "runtime-slash", cliCommand: "memory" }],
        }),
      );
      fs.writeFileSync(
        path.join(memoryOwnerDir, "index.cjs"),
        [
          'const fs = require("node:fs");',
          "module.exports = {",
          '  id: "memory-owner",',
          "  register(api) {",
          '    fs.writeFileSync(process.env.MEMORY_OWNER_MARKER, "loaded", "utf8");',
          "    api.registerCli(({ program }) => {",
          '      const memory = program.command("memory");',
          '      memory.command("status").option("--agent <id>").option("--json").action(() => console.log("[]"));',
          '      memory.command("search").argument("<query>").option("--agent <id>").option("--json").action(() => console.log("{\\\"results\\\":[]}"));',
          '    }, { descriptors: [{ name: "memory", description: "Memory fixture", hasSubcommands: true }] });',
          "  },",
          "};",
          "",
        ].join("\n"),
      );
    }
    fs.mkdirSync(stateDir, { recursive: true });
    const configPath = path.join(stateDir, "openclaw.json");
    fs.writeFileSync(
      configPath,
      JSON.stringify({
        agents: { defaults: { workspace: workspaceDir } },
        plugins: {
          load: { paths: [pluginDir, ...(needsMemoryOwner ? [memoryOwnerDir] : [])] },
          entries: {
            [fixture.pluginId]: { enabled: true },
            ...(needsMemoryOwner ? { "memory-owner": { enabled: true } } : {}),
          },
        },
      }),
    );

    const result = await execFileAsync(
      process.execPath,
      ["--import", "tsx", "src/entry.ts", ...args],
      {
        cwd: path.resolve("."),
        env: {
          ...process.env,
          // This fixture owns command imports, not entrypoint respawn or compile-cache behavior.
          NODE_DISABLE_COMPILE_CACHE: "1",
          NODE_ENV: undefined,
          VITEST: undefined,
          OPENCLAW_CLAWHUB_URL: clawHubUrl,
          OPENCLAW_CONFIG_PATH: configPath,
          MEMORY_OWNER_MARKER: path.join(root, "memory-owner-loaded"),
          OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
          OPENCLAW_DEV_SOURCE_ROOT: path.resolve("."),
          OPENCLAW_HOME: path.join(root, "home"),
          OPENCLAW_NO_RESPAWN: "1",
          OPENCLAW_STATE_DIR: stateDir,
        },
        maxBuffer: 4 * 1024 * 1024,
        timeout: 60_000,
      },
    );
    expect(`${result.stdout}\n${result.stderr}`).not.toContain(
      `${label} imported an unrelated plugin runtime`,
    );
    expect(isColdPluginRuntimeLoaded(fixture)).toBe(false);
    if (needsMemoryOwner) {
      expect(fs.existsSync(path.join(root, "memory-owner-loaded"))).toBe(true);
    }
  },
  150_000,
);
