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

function runTransHelp(...args: string[]): string {
  const result = spawnSync("bun", ["scripts/ssh-cli.ts", ...args], {
    cwd: repoRoot,
    env: {
      ...process.env,
      UNIDESK_SSH_ENTRYPOINT: "trans",
      UNIDESK_TRANS_REPO_ROOT: repoRoot,
    },
    encoding: "utf8",
  });
  expect(result.status).toBe(0);
  expect(result.stderr).toBe("");
  return result.stdout;
}

describe("ssh bounded progressive help", () => {
  test("selects exact help-first operation scopes", () => {
    expect(sshHelpScope(["ssh", "--help", "download"])).toBe("download");
    expect(sshHelpScope(["ssh", "help", "apply-patch"])).toBe("apply-patch");
    expect(sshHelpScope(["ssh", "--help", "ps"])).toBe("ps");
    expect(sshHelpScope(["ssh", "--help"])).toBeUndefined();
    expect(sshHelpScope(["ssh", "--help", "unknown"])).toBeUndefined();
    expect(sshHelpScope(["ssh", "NC01:k3s", "--help"])).toBeUndefined();
    expect(sshHelpScope(["ssh", "--help", "download", "extra"])).toBeUndefined();
  });

  test("keeps the top-level model compact while indexing routes and every scoped operation", () => {
    const top = sshHelp() as {
      routeSyntax: Record<string, string>;
      operationGroups: Record<string, string>;
      scopedHelp: { command: string; availableOperationCount: number };
      outputTruncated?: boolean;
    };
    const serialized = JSON.stringify(top);
    const grouped = Object.values(top.operationGroups).flatMap((value) => value.split(" | "));

    expect(Buffer.byteLength(serialized, "utf8")).toBeLessThan(6_144);
    expect(top.outputTruncated).toBeUndefined();
    expect(top.routeSyntax.general).toContain("<route> <operation>");
    expect(top.routeSyntax.k3sWorkload).toContain(":<namespace>:<workload>");
    expect(top.routeSyntax.windowsWorkspace).toContain(":win");
    expect(top.operationGroups.patch).toBe("apply-patch | apply-patch-v1");
    expect(top.operationGroups.transfer).toBe("upload | download");
    expect(new Set(grouped).size).toBe(top.scopedHelp.availableOperationCount);
    expect(top.scopedHelp.command).toContain("--help <operation>");
    for (const operation of grouped) {
      const scope = sshHelpScope(["ssh", "--help", operation]);
      expect(scope).toBe(operation);
      expect(Buffer.byteLength(JSON.stringify(sshHelp(scope)), "utf8")).toBeLessThan(6_144);
    }
  });

  test("keeps download help bounded and discloses the executing source root", () => {
    const scoped = sshHelp("download") as {
      scope: string;
      runtime: { repoRoot: string; cliPath: string; cwdSelectsRepoRoot: boolean; rootSelection: { environmentVariable: string } };
      usage: string[];
      examples: Record<string, string>;
      contract: { pathOrder: string[]; transport: string; verification: { automatic: boolean } };
    };
    const serialized = JSON.stringify(scoped);

    expect(scoped.scope).toBe("download");
    expect(scoped.usage.length).toBeLessThanOrEqual(4);
    expect(Buffer.byteLength(serialized, "utf8")).toBeLessThan(6_144);
    expect(serialized).not.toContain("git exact-commit");
    expect(scoped.runtime.repoRoot).toBe(repoRoot);
    expect(scoped.runtime.cliPath).toBe(`${repoRoot}/scripts/ssh-cli.ts`);
    expect(scoped.runtime.cwdSelectsRepoRoot).toBe(false);
    expect(scoped.runtime.rootSelection.environmentVariable).toBe("UNIDESK_TRANS_REPO_ROOT");
    expect(scoped.contract.pathOrder).toEqual(["remote-file", "local-file"]);
    expect(scoped.contract.transport).toBe("host.ssh.tcp-pool");
    expect(scoped.contract.verification.automatic).toBe(true);
    expect(scoped.usage[0]).toBe("trans <route> cat <remote-text-file>");
    expect(serialized).toContain("text-transfer-discouraged");
    expect(serialized).toContain("--allow-text-transfer");
    expect(scoped.examples.host).toContain("download /tmp/firmware.bin ./firmware.bin");
    expect(scoped.examples.k3sWorkload).toBe(
      "trans NC01:k3s:hwlab-v03:hwlab-cloud-api:hwlab-cloud-api download /tmp/runtime-bundle.tar.gz ./runtime-bundle.tar.gz",
    );
    expect(scoped.examples.windowsDrive).toContain("D:\\tmp\\capture.zip");
    expect(scoped.examples.generatedTextOverride).toBe(
      "trans D601:/tmp download --allow-text-transfer /tmp/trace.json ./trace.json",
    );
    expect(scoped.examples.host).not.toMatch(/\.(?:json|md|txt|ya?ml)\b/);
    expect(scoped.examples.k3sWorkload).not.toMatch(/\.(?:json|md|txt|ya?ml)\b/);
    expect(scoped.examples.windowsDrive).not.toMatch(/\.(?:json|md|txt|ya?ml)\b/);
  });

  test("renders compact top-level and scoped help without dump fallback", () => {
    const top = runTransHelp("--help");
    expect(Buffer.byteLength(top, "utf8")).toBeLessThan(2_048);
    expect(top.trim().split("\n")).toHaveLength(11);
    expect(top).toContain("usage: trans <route> <operation>");
    expect(top).toContain("transfer: upload | download");
    expect(top).toContain("scoped help: trans --help <operation>");
    expect(top).toContain("cwd: host/workspace uses <provider>:/absolute/workspace; k3s exec uses --cwd");
    expect(top).not.toContain("outputTruncated");
    expect(top).not.toContain("dump");

    const routeTop = runTransHelp("NC01:k3s", "--help");
    expect(routeTop).toBe(top);

    const applyPatch = runTransHelp("--help", "apply-patch");
    expect(applyPatch.trim().split("\n").length).toBeLessThanOrEqual(8);
    expect(applyPatch).toContain("TRANS HELP  apply-patch");
    expect(applyPatch).toContain("usage: trans <route> apply-patch <<'PATCH'");
    expect(applyPatch).not.toContain("patch.diff");
    expect(applyPatch).not.toContain("outputTruncated");

    const exec = runTransHelp("--help", "exec");
    expect(exec).toContain("trans <provider>:/absolute/workspace exec <command>");
    expect(exec).toContain("k3s:<namespace>:<workload>");

    const download = runTransHelp("--help", "download");
    expect(download.trim().split("\n").length).toBeLessThanOrEqual(8);
    expect(download).toContain("TRANS HELP  download");
    expect(download.indexOf("cat <remote-text-file>")).toBeLessThan(download.indexOf("download <remote-file> <local-file>"));
    expect(download).toContain("download <remote-file> <local-file>");
    expect(download).not.toContain("outputTruncated");

    const upload = runTransHelp("--help", "upload");
    expect(upload.trim().split("\n").length).toBeLessThanOrEqual(8);
    expect(upload.indexOf("apply-patch <<'PATCH'")).toBeLessThan(upload.indexOf("upload <local-file> <remote-file>"));
    expect(upload).toContain("text-transfer-discouraged");
  });

  test("returns the existing bounded JSON model only for explicit machine output", () => {
    const top = JSON.parse(runTransHelp("--help", "--json")) as {
      ok: boolean;
      command: string;
      data: { command: string; output: string; routeSyntax: Record<string, string>; dump?: unknown };
    };
    expect(top.ok).toBe(true);
    expect(top.command).toBe("trans --help --json");
    expect(top.data.command).toBe("trans --help");
    expect(top.data.output).toBe("json");
    expect(top.data.routeSyntax.general).toContain("<route> <operation>");
    expect(top.data.dump).toBeUndefined();

    const prefixed = JSON.parse(runTransHelp("--json", "--help")) as typeof top;
    expect(prefixed.ok).toBe(true);
    expect(prefixed.data.command).toBe("trans --help");

    const topOutput = JSON.parse(runTransHelp("--help", "-o", "json")) as typeof top;
    expect(topOutput.ok).toBe(true);
    expect(topOutput.data.output).toBe("json");

    const scoped = JSON.parse(runTransHelp("--help", "download", "-o", "json")) as {
      ok: boolean;
      data: { command: string; output: string; scope: string; runtime: { repoRoot: string }; dump?: unknown };
    };
    expect(scoped.ok).toBe(true);
    expect(scoped.data.command).toBe("trans --help download");
    expect(scoped.data.output).toBe("json");
    expect(scoped.data.scope).toBe("download");
    expect(scoped.data.runtime.repoRoot).toBe(repoRoot);
    expect(scoped.data.dump).toBeUndefined();

    const routeTop = JSON.parse(runTransHelp("NC01:k3s", "--help", "--json")) as typeof top;
    expect(routeTop.ok).toBe(true);
    expect(routeTop.data.command).toBe("trans --help");
  });

  test("returns compact no-stack input errors for unknown help scopes", () => {
    const result = spawnSync("bun", ["scripts/ssh-cli.ts", "--help", "unknown-operation"], {
      cwd: repoRoot,
      env: { ...process.env, UNIDESK_SSH_ENTRYPOINT: "trans", UNIDESK_TRANS_REPO_ROOT: repoRoot },
      encoding: "utf8",
    });
    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-help-scope");
    expect(result.stdout).not.toContain("stack");
    expect(result.stdout).not.toContain(" at ");
  });

  test("rejects unsupported shell options locally with compact correction paths", () => {
    for (const outputArgs of [[], ["--json"]]) {
      const result = spawnSync("bun", ["scripts/ssh-cli.ts", "NC01:/root/unidesk", "sh", "-c", "git status --short", ...outputArgs], {
        cwd: repoRoot,
        env: { ...process.env, UNIDESK_SSH_ENTRYPOINT: "trans", UNIDESK_TRANS_REPO_ROOT: repoRoot },
        encoding: "utf8",
      });
      expect(result.status).toBe(1);
      expect(result.stderr).toBe("");
      expect(result.stdout).toContain("ssh-sh-option-unsupported");
      expect(result.stdout).toContain("trans <route> sh <<'SH' ... SH");
      expect(result.stdout).toContain("trans <route> <command> [args...]");
      expect(result.stdout).not.toContain("scripts/src/ssh.ts");
      expect(result.stdout).not.toContain("ssh-runtime.ts");
      expect(result.stdout).not.toContain("\"stack\"");
    }
  });

  test("rejects the moved root entrypoint locally with one exact replacement", () => {
    const result = spawnSync("bun", ["scripts/cli.ts", "trans", "NC01:/root/unidesk", "git", "status", "--short"], {
      cwd: repoRoot,
      env: { ...process.env, UNIDESK_TRANS_REPO_ROOT: repoRoot },
      encoding: "utf8",
    });
    expect(result.status).toBe(1);
    expect(result.stderr).toBe("");
    expect(result.stdout).toContain("ERROR  cli-input/trans-root-cli-entrypoint-moved");
    expect(result.stdout).toContain("usage: trans NC01:/root/unidesk git status --short");
    expect(result.stdout).not.toContain("stack");
  });
});
