import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { Command } from "commander";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

const stderrWrites = vi.hoisted(() => vi.fn());
const getCoreCliCommandNamesMock = vi.hoisted(() => vi.fn(() => []));
const registerCoreCliByNameMock = vi.hoisted(() => vi.fn());
const getProgramContextMock = vi.hoisted(() => vi.fn(() => null));
const getSubCliEntriesMock = vi.hoisted(() =>
  vi.fn(() => [
    { name: "qa", description: "QA commands", hasSubcommands: true },
    { name: "completion", description: "Completion", hasSubcommands: false },
  ]),
);
const registerSubCliByNameMock = vi.hoisted(() =>
  vi.fn(async (program: Command, name: string) => {
    if (name === "qa") {
      throw new Error("qa scenario pack not found: qa/scenarios/index.md");
    }
    program.command(name);
    return true;
  }),
);
const registerPluginCliCommandsFromValidatedConfigMock = vi.hoisted(() => vi.fn(async () => null));

vi.mock("./program/command-registry-core.js", () => ({
  getCoreCliCommandNames: getCoreCliCommandNamesMock,
  registerCoreCliByName: registerCoreCliByNameMock,
}));

vi.mock("./program/program-context.js", () => ({
  getProgramContext: getProgramContextMock,
}));

vi.mock("./program/register.subclis-core.js", () => ({
  getSubCliEntries: getSubCliEntriesMock,
  registerSubCliByName: registerSubCliByNameMock,
}));

vi.mock("../plugins/cli.js", () => ({
  registerPluginCliCommandsFromValidatedConfig: registerPluginCliCommandsFromValidatedConfigMock,
}));

describe("completion-cli write-state", () => {
  const originalHome = process.env.HOME;
  const originalStateDir = process.env.OPENCLAW_STATE_DIR;
  let restoreStderrWriteSpy: (() => void) | null = null;

  beforeEach(() => {
    stderrWrites.mockReset();
    getCoreCliCommandNamesMock.mockClear();
    registerCoreCliByNameMock.mockClear();
    getProgramContextMock.mockClear();
    getSubCliEntriesMock.mockClear();
    registerSubCliByNameMock.mockClear();
    registerPluginCliCommandsFromValidatedConfigMock.mockClear();
    const stderrWriteSpy = vi.spyOn(process.stderr, "write").mockImplementation(((
      chunk: string | Uint8Array,
    ) => {
      stderrWrites(chunk.toString());
      return true;
    }) as typeof process.stderr.write);
    restoreStderrWriteSpy = () => stderrWriteSpy.mockRestore();
  });

  afterEach(async () => {
    restoreStderrWriteSpy?.();
    if (originalHome === undefined) {
      delete process.env.HOME;
    } else {
      process.env.HOME = originalHome;
    }
    if (originalStateDir === undefined) {
      delete process.env.OPENCLAW_STATE_DIR;
    } else {
      process.env.OPENCLAW_STATE_DIR = originalStateDir;
    }
  });

  it("keeps completion cache generation alive when a subcli fails to register", async () => {
    const { registerCompletionCli } = await import("./completion-cli.js");
    const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-completion-state-"));
    const homeDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-completion-home-"));

    process.env.OPENCLAW_STATE_DIR = stateDir;
    process.env.HOME = homeDir;

    const program = new Command();
    program.name("openclaw");
    registerCompletionCli(program);

    await program.parseAsync(["completion", "--write-state"], { from: "user" });

    const cacheDir = path.join(stateDir, "completions");
    expect(await fs.readdir(cacheDir)).toEqual(
      expect.arrayContaining(["openclaw.bash", "openclaw.fish", "openclaw.ps1", "openclaw.zsh"]),
    );
    expect(registerSubCliByNameMock).toHaveBeenCalledWith(program, "qa");
    expect(registerPluginCliCommandsFromValidatedConfigMock).toHaveBeenCalledTimes(1);
    expect(stderrWrites).toHaveBeenCalledWith(
      expect.stringContaining("skipping subcommand `qa` while building completion cache"),
    );

    await fs.rm(stateDir, { recursive: true, force: true });
    await fs.rm(homeDir, { recursive: true, force: true });
  });
});
