import { spawnSync } from "node:child_process";
import { describe, expect, test } from "bun:test";
import { repoRoot } from "./config";

describe("CLI input error hierarchy", () => {
  test("unknown root commands are compact and stack-free by default", () => {
    const result = runCli("definitely-not-a-command");
    expect(result.status).toBe(1);
    expect(result.stderr).toBe("");
    expect(result.stdout.trim().split("\n").length).toBeLessThanOrEqual(8);
    expect(result.stdout).toContain("ERROR  cli-input/unknown-command");
    expect(result.stdout).toContain("usage: bun scripts/cli.ts help");
    expect(result.stdout).not.toContain("stack");
    expect(result.stdout).not.toContain(" at ");
  });

  test("explicit machine output returns a JSON input-error envelope without a stack", () => {
    for (const args of [
      ["definitely-not-a-command", "--json"],
      ["definitely-not-a-command", "--full"],
      ["definitely-not-a-command", "--raw"],
      ["definitely-not-a-command", "-o", "json"],
    ]) {
      const result = runCli(...args);
      expect(result.status).toBe(1);
      expect(result.stderr).toBe("");
      const payload = JSON.parse(result.stdout) as {
        ok: boolean;
        error: { kind: string; code: string; debug: boolean; stack?: string };
      };
      expect(payload.ok).toBe(false);
      expect(payload.error.kind).toBe("cli-input");
      expect(payload.error.code).toBe("unknown-command");
      expect(payload.error.debug).toBe(false);
      expect(payload.error.stack).toBeUndefined();
      expect(result.stdout).not.toContain("stackOmitted");
    }

    const nonJsonOutput = runCli("definitely-not-a-command", "-o", "yaml");
    expect(nonJsonOutput.status).toBe(1);
    expect(nonJsonOutput.stdout).toContain("ERROR  cli-input/unknown-command");
    expect(() => JSON.parse(nonJsonOutput.stdout)).toThrow();
  });

  test("predictable Gitea option validation is compact and stack-free", () => {
    const result = runCli("platform-infra", "gitea", "status", "--target", "NC01", "--definitely-unsupported");
    expect(result.status).toBe(1);
    expect(result.stderr).toBe("");
    expect(result.stdout.trim().split("\n").length).toBeLessThanOrEqual(8);
    expect(result.stdout).toContain("ERROR  cli-input/unsupported-option");
    expect(result.stdout).toContain("argument: --definitely-unsupported");
    expect(result.stdout).not.toContain("stack");
    expect(result.stdout).not.toContain("platform-infra-gitea.ts:");
  });

  test("web-probe screenshot returns a typed non-mutating correction for --url with --path", () => {
    const result = runCli(
      "web-probe",
      "screenshot",
      "--node",
      "NC01",
      "--lane",
      "v03",
      "--url",
      "https://custom.example/workbench",
      "--path",
      "/workbench",
      "--json",
    );
    expect(result.status).toBe(1);
    expect(result.stderr).toBe("");
    const payload = JSON.parse(result.stdout) as {
      ok: boolean;
      error: {
        kind: string;
        code: string;
        reason: string;
        mutation: boolean;
        correctionCommands: string[];
      };
    };
    expect(payload.ok).toBe(false);
    expect(payload.error.kind).toBe("cli-input");
    expect(payload.error.code).toBe("web-probe-screenshot-url-path-mutually-exclusive");
    expect(payload.error.reason).toBe("custom-url-already-includes-full-path-remove-path");
    expect(payload.error.mutation).toBe(false);
    expect(payload.error.correctionCommands).toEqual([
      "bun scripts/cli.ts web-probe 'screenshot' '--node' 'NC01' '--lane' 'v03' '--url' 'https://custom.example/workbench'",
    ]);
  });

  test("Gitea full and raw input errors preserve machine JSON without a stack", () => {
    for (const outputFlag of ["--full", "--raw"]) {
      const result = runCli("platform-infra", "gitea", "status", "--target", "NC01", outputFlag, "--definitely-unsupported");
      expect(result.status).toBe(1);
      expect(result.stderr).toBe("");
      const payload = JSON.parse(result.stdout) as {
        ok: boolean;
        error: { kind: string; code: string; argument: string; debug: boolean; stack?: string };
      };
      expect(payload.ok).toBe(false);
      expect(payload.error.kind).toBe("cli-input");
      expect(payload.error.code).toBe("unsupported-option");
      expect(payload.error.argument).toBe("--definitely-unsupported");
      expect(payload.error.debug).toBe(false);
      expect(payload.error.stack).toBeUndefined();
    }
  });

  test("only an explicitly unknown command target is converted to a compact input error", () => {
    const unknownTarget = runCli("platform-infra", "gitea", "status", "--target", "definitely-nope");
    expect(unknownTarget.status).toBe(1);
    expect(unknownTarget.stderr).toBe("");
    expect(unknownTarget.stdout.trim().split("\n").length).toBeLessThanOrEqual(8);
    expect(unknownTarget.stdout).toContain("ERROR  cli-input/unknown-target");
    expect(unknownTarget.stdout).toContain("argument: definitely-nope");
    expect(unknownTarget.stdout).not.toContain("stack");

    const internalConfig = spawnSync("bun", ["-e", [
      "import { emitError } from './scripts/src/output.ts';",
      "import { resolveTarget } from './scripts/src/platform-infra-gitea-config.ts';",
      "try { resolveTarget({ defaults: { targetId: 'NC01' }, targets: [] } as any, 'NC01'); }",
      "catch (error) { emitError('internal-gitea-config-probe', error); }",
    ].join(" ")], {
      cwd: repoRoot,
      env: process.env,
      encoding: "utf8",
    });
    expect(internalConfig.status).toBe(0);
    const internalPayload = JSON.parse(internalConfig.stdout) as { error: { message: string; stack: string } };
    expect(internalPayload.error.message).toContain("unknown gitea target NC01");
    expect(internalPayload.error.stack).toContain("platform-infra-gitea-config.ts");
  });

  test("explicit debug retains stack while internal exceptions remain observable", () => {
    const debug = runCliWithEnv({ UNIDESK_CLI_DEBUG: "1" }, "definitely-not-a-command");
    expect(debug.status).toBe(1);
    const debugPayload = JSON.parse(debug.stdout) as { error: { debug: boolean; stack: string } };
    expect(debugPayload.error.debug).toBe(true);
    expect(debugPayload.error.stack).toContain("CliInputError: Unknown command");

    const internal = spawnSync("bun", ["-e", "import { emitError } from './scripts/src/output.ts'; emitError('internal-probe', new Error('internal-probe-failed'));"], {
      cwd: repoRoot,
      env: process.env,
      encoding: "utf8",
    });
    expect(internal.status).toBe(0);
    const internalPayload = JSON.parse(internal.stdout) as { error: { message: string; stack: string } };
    expect(internalPayload.error.message).toBe("internal-probe-failed");
    expect(internalPayload.error.stack).toContain("Error: internal-probe-failed");
  });
});

function runCli(...args: string[]) {
  return runCliWithEnv({}, ...args);
}

function runCliWithEnv(env: NodeJS.ProcessEnv, ...args: string[]) {
  return spawnSync("bun", ["scripts/cli.ts", ...args], {
    cwd: repoRoot,
    env: { ...process.env, ...env },
    encoding: "utf8",
  });
}
