import { createHash } from "node:crypto";
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { describe, expect, test } from "bun:test";
import type { UniDeskConfig } from "./config";
import {
  createPosixApplyPatchFileSystem,
  createSshStderrForwarder,
  createSshStdoutForwarder,
  formatSshStdoutTruncationHint,
  formatSshTruncationCompletionSummary,
  parseSshInvocation,
  remoteCommandForRoute,
  sshTruncationCompletionSummary,
  sshCaptureBackendPlan,
  sshCaptureRuntimeTimeoutMs,
  sshStderrStreamMaxBytes,
  sshStreamDumpExplicitlyRequested,
  sshStdoutStreamMaxBytes,
  sshStdoutTruncationHint,
  sshWindowsPlaneHint,
  sshWindowsPowerShellPipelineHint,
  windowsFsReadOnlyScript,
  windowsPowerShellScriptPrelude,
} from "./ssh";
import { readCliOutputPolicy } from "./output";

describe("ssh capture runtime timeout", () => {
  test("keeps explicit long-poll transport margin beyond the 60m user timeout", () => {
    expect(sshCaptureRuntimeTimeoutMs(60 * 60_000 + 15_000)).toBe(60 * 60_000 + 15_000);
    expect(sshCaptureRuntimeTimeoutMs(62 * 60_000)).toBe(61 * 60_000);
    expect(() => sshCaptureRuntimeTimeoutMs(0)).toThrow("positive integer");
  });
});

function sha256Hex(text: string): string {
  return createHash("sha256").update(text, "utf8").digest("hex");
}

function decodedWindowsPowerShellCommand(remoteCommand: string | null): string {
  const encoded = remoteCommand?.match(/'([A-Za-z0-9+/=]+)'$/u)?.[1];
  if (encoded === undefined) throw new Error("Windows PowerShell command did not end in an encoded payload");
  return Buffer.from(encoded, "base64").toString("utf16le");
}

function minimalConfig(publicHost = "203.0.113.10"): UniDeskConfig {
  return {
    network: { publicHost },
  } as UniDeskConfig;
}

describe("ssh windows PowerShell safety prelude", () => {
  test("shadows ConvertTo-Json and strips filesystem ETS metadata", () => {
    const prelude = windowsPowerShellScriptPrelude("C:\\test");

    expect(prelude).toContain("function ConvertTo-UniDeskPlainJsonValue");
    expect(prelude).toContain("function ConvertTo-Json");
    expect(prelude).toContain("'PSPath','PSParentPath','PSChildName','PSDrive','PSProvider','ReadCount'");
    expect(prelude).toContain("Microsoft.PowerShell.Utility\\ConvertTo-Json");
    expect(prelude).toContain("$PSDefaultParameterValues['Get-Content:Encoding'] = 'utf8'");
    expect(prelude).toContain("[System.IO.Directory]::Exists($unideskRouteCwd)");
    expect(prelude).toContain("Set-Location -LiteralPath $unideskRouteCwd -ErrorAction Stop");
    expect(prelude).toContain("[Environment]::CurrentDirectory = (Get-Location).ProviderPath");
    expect(prelude).toContain("UNIDESK_SSH_CWD_FAILED");
    expect(prelude).toContain("trans-windows-route-cwd-invalid");
  });
});

describe("ssh windows fs read-only operations", () => {
  test("routes common read-only commands through the Windows fs backend", () => {
    const invocation = parseSshInvocation("D601:win/c/test", ["cat", "hello.md"]);
    const rgInvocation = parseSshInvocation("D601:win/c/test", ["rg", "-i", "needle", "."]);

    expect(invocation.route.plane).toBe("win");
    expect(invocation.route.workspace).toBe("C:\\test");
    expect(invocation.parsed.invocationKind).toBe("helper");
    expect(invocation.parsed.requiresStdin).toBe(false);
    expect(invocation.parsed.remoteCommand).toContain("powershell.exe");
    expect(invocation.parsed.remoteCommand).toContain("-EncodedCommand");
    expect(rgInvocation.parsed.invocationKind).toBe("helper");
    expect(rgInvocation.parsed.requiresStdin).toBe(false);
  });

  test("routes git status, diff, and commit through Windows cmd convenience", () => {
    const statusInvocation = parseSshInvocation("D601:win/F/Work/ConStart", ["git", "status", "--short", "--branch"]);
    const diffInvocation = parseSshInvocation("D601:win/F/Work/ConStart", ["git", "diff", "--check"]);
    const commitInvocation = parseSshInvocation("D601:win/F/Work/ConStart", ["git", "commit", "-m", "fix: update docs"]);

    expect(statusInvocation.route.plane).toBe("win");
    expect(statusInvocation.route.workspace).toBe("F:\\Work\\ConStart");
    expect(statusInvocation.parsed.invocationKind).toBe("argv");
    expect(statusInvocation.parsed.requiresStdin).toBe(false);
    expect(statusInvocation.parsed.remoteCommand).toContain("powershell.exe");
    expect(diffInvocation.parsed.invocationKind).toBe("argv");
    expect(diffInvocation.parsed.requiresStdin).toBe(false);
    expect(commitInvocation.parsed.invocationKind).toBe("argv");
    expect(commitInvocation.parsed.requiresStdin).toBe(false);
  });

  test("builds controlled exact-commit and object status helpers", () => {
    const status = parseSshInvocation("D601:win/F/Work/ConStart", ["git", "exact-commit", "status"]);
    const plan = parseSshInvocation("D601:win/F/Work/ConStart", ["git", "exact-commit", "plan", "--path", "src/main.c"]);
    const run = parseSshInvocation("D601:win/F/Work/ConStart", ["git", "exact-commit", "run", "--path", "src/main.c", "--message", "fix: exact", "--confirm"]);
    const decoded = decodedWindowsPowerShellCommand(run.parsed.remoteCommand);

    expect(status.parsed.invocationKind).toBe("helper");
    expect(plan.parsed.invocationKind).toBe("helper");
    expect(decoded).toContain("GIT_INDEX_FILE");
    expect(decoded).toContain("update-ref");
    expect(decoded).toContain("unselected worktree/index fingerprint");
    expect(decoded).toContain("old-value CAS");
    expect(decoded).toContain("$ErrorActionPreference='Continue'");
    expect(decoded).toContain("2>&1");
    expect(decoded).toContain("Management.Automation.ErrorRecord");
    expect(decoded).toContain("warnings=@($gitWarnings)");
    expect(decoded.trimEnd()).toEndWith("exit 0");
    expect(() => parseSshInvocation("D601:win/F/Work/ConStart", ["git", "exact-commit", "run", "--path", "src/main.c", "--message", "fix: exact"])).toThrow("requires --confirm");
    expect(() => parseSshInvocation("D601:win/F/Work/ConStart", ["git", "exact-commit", "plan", "--path", "../secret"])).toThrow("must stay inside");
  });

  test("adds a bounded hint for the PowerShell statement-to-pipeline parser boundary", () => {
    const invocation = parseSshInvocation("D601:win/c/test", ["ps", "foreach ($x in $xs) { $x } | Format-List"]);
    const hint = sshWindowsPowerShellPipelineHint(invocation, 1, "An empty pipe element is not allowed");

    expect(hint).toContain("windows-powershell-statement-pipeline-boundary");
    expect(hint).toContain("@(foreach (...) { ... }) | Format-List");
    expect(hint).not.toContain("$x in $xs");
    expect(sshWindowsPowerShellPipelineHint(invocation, 0, "An empty pipe element is not allowed")).toBe("");
  });

  test("builds bounded UTF-8 scripts for cat, ls, and rg", () => {
    const catScript = windowsFsReadOnlyScript("F:\\Work\\demo", "cat", ["--max-bytes", "4096", "中文.md"]);
    const lsScript = windowsFsReadOnlyScript("F:\\Work\\demo", "ls", ["-la", "--limit=5"]);
    const rgScript = windowsFsReadOnlyScript("F:\\Work\\demo", "rg", ["-i", "--max-count=5", "needle", "."]);

    expect(catScript).toContain("$operation = 'cat';");
    expect(catScript).toContain("F:\\Work\\demo");
    expect(catScript).toContain("binary file refused by windows fs read operation");
    expect(catScript).toContain("file is not valid UTF-8");
    expect(catScript).toContain("file too large for windows fs read operation");
    expect(lsScript).toContain("$operation = 'ls';");
    expect(lsScript).toContain("UNIDESK_WINDOWS_FS_TRUNCATED");
    expect(lsScript).toContain("TYPE BYTES UPDATED NAME");
    expect(rgScript).toContain("$operation = 'rg';");
    expect(rgScript).toContain("unsupported rg option on Windows route");
    expect(rgScript).toContain("UNIDESK_WINDOWS_RG_SUMMARY");
    expect(rgScript).toContain("UNIDESK_WINDOWS_RG_HINT code=timeout exitCode=124 action=narrow-scope-or-add-glob");
    expect(rgScript).toContain("engine=native-rg");
    expect(rgScript).toContain("config/unidesk-cli.yaml#trans.windowsFs.rg");
    expect(rgScript).toContain("--files");
    expect(rgScript).toContain("--glob");
    expect(rgScript).toContain("--with-filename");
    expect(rgScript).toContain("encoding=");
  });

  test("supports repeated rg patterns and streams large-file tail", () => {
    const rgScript = windowsFsReadOnlyScript("F:\\Work\\demo", "rg", ["-n", "-e", "alpha", "--regexp", "beta", "src/test.c"]);
    const tailScript = windowsFsReadOnlyScript("F:\\Work\\demo", "tail", ["-n", "40", ".state/request_history.jsonl"]);

    expect(rgScript).toContain("$patterns.Add($toolArgs[$i])");
    expect(rgScript).toContain("$nativeArgs.Add('--regexp')");
    expect(rgScript).toContain("fileCount=");
    expect(tailScript).toContain("[IO.StreamReader]::new");
    expect(tailScript).toContain("[Collections.Generic.Queue[string]]::new()");
    expect(tailScript).not.toContain("Read-StrictText (Resolve-UnideskPath $paths[0]) 1048576");
  });

  test("supports wc flags, exact skill lookup, and Windows cmd argv boundaries", () => {
    const wcScript = windowsFsReadOnlyScript("F:\\Work\\demo", "wc", ["-l", "docs/read me.md"]);
    const skills = parseSshInvocation("D601:win", ["skills", "--scope", "agents", "--name", "mdtodo-edit"]);
    const cmd = parseSshInvocation("D601:win/F/Work/demo", ["cmd", "python", "tool.py", "71-FILTER 高频输出精度与频率跳变改进"]);

    expect(wcScript).toContain("$arg -eq '-l'");
    expect(wcScript).toContain("unsupported wc option on Windows route");
    expect(decodedWindowsPowerShellCommand(skills.parsed.remoteCommand)).toContain("$exactName = 'mdtodo-edit'");
    expect(decodedWindowsPowerShellCommand(cmd.parsed.remoteCommand)).toContain('python tool.py "71-FILTER 高频输出精度与频率跳变改进"');
  });

  test("supports GNU head/tail counts and preserves PowerShell native argv", () => {
    const headScript = windowsFsReadOnlyScript("F:\\Work\\demo", "head", ["-35", "docs/read me.md"]);
    const tailScript = windowsFsReadOnlyScript("F:\\Work\\demo", "tail", ["-20", "docs/read me.md"]);
    const pythonCode = 'import pathlib; print({"utf8": pathlib.Path(r"docs/报告.md").read_text(encoding="utf-8") != ""})';
    const python = parseSshInvocation("D601:win/F/Work/demo", ["ps", "python", "-c", pythonCode]);
    const launcher = decodedWindowsPowerShellCommand(python.parsed.remoteCommand);
    const encodedPayload = launcher.match(/\$payloadJsonB64 = ''([A-Za-z0-9+/=]+)'';/u)?.[1];

    expect(headScript).toContain("$arg -match '^-(\\d+)$'");
    expect(tailScript).toContain("$arg -match '^-(\\d+)$'");
    expect(python.parsed.invocationKind).toBe("argv");
    expect(encodedPayload).toBeDefined();
    expect(JSON.parse(Buffer.from(encodedPayload ?? "", "base64").toString("utf8"))).toEqual({
      command: "python",
      args: ["-c", pythonCode],
      nativeArguments: `"-c" "${pythonCode.replaceAll('"', '\\"')}"`,
    });
  });

  test("requires one route cwd for PowerShell, cmd, and their stdin launchers", () => {
    for (const operation of ["ps", "cmd"] as const) {
      try {
        parseSshInvocation("D601:win", [operation]);
        throw new Error(`expected ${operation} without a route cwd to fail`);
      } catch (error) {
        expect(error).toMatchObject({
          code: "trans-windows-route-cwd-required",
          argument: "D601:win",
        });
        expect((error as Error & { hint?: string }).hint).toContain("quoted heredoc");
      }
    }

    const powerShell = parseSshInvocation("D601:win/d/work/研究资料归档-李昂", ["ps"]);
    const cmd = parseSshInvocation("D601:win/d/work/研究资料归档-李昂", ["cmd"]);
    const powerShellLauncher = decodedWindowsPowerShellCommand(powerShell.parsed.remoteCommand);
    const cmdLauncher = decodedWindowsPowerShellCommand(cmd.parsed.remoteCommand);

    expect(powerShell.parsed.requiresStdin).toBe(true);
    expect(cmd.parsed.requiresStdin).toBe(true);
    for (const launcher of [powerShellLauncher, cmdLauncher]) {
      expect(launcher).toContain("D:\\work\\研究资料归档-李昂");
      expect(launcher).toContain("UNIDESK_SSH_CWD_FAILED");
      expect(launcher).toContain("trans-windows-route-cwd-invalid");
      expect(launcher).toContain("[Environment]::CurrentDirectory = (Get-Location).ProviderPath");
      expect(launcher).toContain("[Console]::In.ReadToEnd()");
    }
    expect(cmdLauncher).not.toContain("cd /d");
  });

  test("rejects unsupported Windows read operations instead of treating them as POSIX", () => {
    expect(() => parseSshInvocation("D601:win/c/test", ["sed", "-n", "1p", "hello.md"])).toThrow("unsupported ssh win operation: sed");
  });

  test("reports route-aware hints for repeated win operation and interactive git commit", () => {
    expect(() => parseSshInvocation("D601:win/F/Work/ConStart", ["win", "ps"])).toThrow("route D601:win/F/Work/ConStart already selects the Windows plane");
    expect(() => parseSshInvocation("D601:win/F/Work/ConStart", ["git", "commit"])).toThrow("ssh win git commit would open an editor");
  });

  test("diagnoses locally expanded PowerShell variables before remote execution", () => {
    try {
      parseSshInvocation("G14-WSL:win/d/Work/demo", ["ps", "= Start-Process python -PassThru; .Id"]);
      throw new Error("expected local PowerShell expansion diagnosis");
    } catch (error) {
      expect(error).toBeInstanceOf(Error);
      const payload = error as Error & {
        code?: string;
        replacementExamples?: { oneLine?: string; pipeline?: string; stdin?: string };
        migrationHint?: string;
      };
      expect(payload.code).toBe("ssh-windows-powershell-local-expansion");
      expect(payload.replacementExamples?.oneLine).toContain("ps '$p = Start-Process python -PassThru; $p.Id'");
      expect(payload.replacementExamples?.pipeline).toContain("Where-Object { $_.CPU -gt 0 }");
      expect(payload.replacementExamples?.stdin).toContain("ps <<'PS'");
      expect(payload.migrationHint).toContain("local single quotes");
    }

    expect(() => parseSshInvocation("G14-WSL:win/d/Work/demo", ["ps", "$p = Start-Process python -PassThru; $p.Id"])).not.toThrow();
  });
});

describe("ssh direct argv boundaries and plane diagnostics", () => {
  test("rejects host/workspace cwd options locally with an exact route replacement", () => {
    try {
      parseSshInvocation("NC01:/root/unidesk", ["exec", "--cwd", "/root/hwlab-v03", "--", "git", "status", "--short"]);
      throw new Error("expected host cwd input error");
    } catch (error) {
      expect(error).toMatchObject({
        code: "trans-host-cwd-belongs-in-route",
        usage: "trans NC01:/root/hwlab-v03 'exec' '--' 'git' 'status' '--short'",
      });
    }
    expect(() => parseSshInvocation("NC01:/root/unidesk", ["bash", "--cwd=/root/hwlab-v03"])).toThrow("cwd belongs in the route");
    expect(() => parseSshInvocation("NC01:k3s:hwlab-v03:hwlab-cloud-api", ["exec", "--cwd", "/workspace", "--", "pwd"])).not.toThrow();
  });

  test("preserves a shell-formed argv token containing spaces and Chinese", () => {
    const invocation = parseSshInvocation("G14-WSL:/mnt/d/Work/CONSTAR_workspace", [
      "python3",
      "/tmp/mdtodo-edit-cli.py",
      "add",
      "docs/MDTODO/example.md",
      "71-FILTER 高频输出精度与频率跳变改进",
    ]);

    expect(invocation.parsed.remoteCommand).toContain("'python3' '/tmp/mdtodo-edit-cli.py' 'add' 'docs/MDTODO/example.md' '71-FILTER 高频输出精度与频率跳变改进'");
  });

  test("suggests the matching Windows plane after WSL command-not-found", () => {
    const invocation = parseSshInvocation("G14-WSL:/mnt/d/Work/CONSTAR_workspace", ["python", "--version"]);
    const hint = sshWindowsPlaneHint(invocation, 127, "sh: python: command not found\n");

    expect(hint).toContain("wsl-command-not-found-check-windows-plane");
    expect(hint).toContain("G14-WSL:win/d/Work/CONSTAR_workspace");
    expect(sshWindowsPlaneHint(invocation, 1, "no match")).toBe("");
  });
});

describe("ssh k3s file transfer command builder", () => {
  test("routes download stream commands through the shared k3s target builder", () => {
    const invocation = parseSshInvocation(
      "NC01:k3s:hwlab:deployment:hwlab-cloud-api",
      ["download", "/tmp/trace.json", "/tmp/trace.json"],
    );
    const remoteCommand = remoteCommandForRoute(
      invocation.route,
      ["sh", "-c", "cat -- \"$1\"", "unidesk-download", "/tmp/trace.json"],
    );

    expect(remoteCommand).toContain("'KUBECONFIG=/etc/rancher/k3s/k3s.yaml'");
    expect(remoteCommand).toContain("'kubectl' 'exec' '-n' 'hwlab'");
    expect(remoteCommand).toContain("'deployment/hwlab-cloud-api'");
    expect(remoteCommand).toContain("'cat -- \"$1\"'");
    expect(remoteCommand).toContain("'/tmp/trace.json'");
  });
});

describe("ssh host apply-patch fs backend", () => {
  test("uses POSIX bulk fs operations for host routes", async () => {
    const invocation = parseSshInvocation("D601:/mnt/f/Work/ConStart", ["apply-patch"]);
    const text = "alpha\nold\nomega\n";
    const calls: Array<{ operation: string; args: string[]; input?: string }> = [];
    const fs = createPosixApplyPatchFileSystem(invocation, async (command, input) => {
      const operation = command[4] ?? "";
      const args = command.slice(5);
      calls.push({ operation, args, input });
      if (operation === "read-bulk-b64") {
        return {
          exitCode: 0,
          stderr: "",
          stdout: [
            "UNIDESK_APPLY_PATCH_V2_BULK_READ 1",
            [
              Buffer.from("docs/test.md", "utf8").toString("base64"),
              String(Buffer.byteLength(text, "utf8")),
              sha256Hex(text),
              Buffer.from(text, "utf8").toString("base64"),
            ].join(" "),
            "",
          ].join("\n"),
        };
      }
      if (operation === "apply-replacements-bulk-stdin") {
        expect(input).toContain(Buffer.from("docs/test.md", "utf8").toString("base64"));
        return { exitCode: 0, stdout: "", stderr: "" };
      }
      throw new Error(`unexpected operation ${operation}`);
    });

    const files = await fs.readFiles!(["docs/test.md"]);
    await fs.applyReplacementsBulk!(["docs/test.md"], new Map([[
      "docs/test.md",
      {
        path: "docs/test.md",
        originalBytes: Buffer.byteLength(text, "utf8"),
        originalSha256: sha256Hex(text),
        finalBytes: Buffer.byteLength("alpha\nnew\nomega\n", "utf8"),
        finalSha256: sha256Hex("alpha\nnew\nomega\n"),
        newlineStyle: "lf",
        encodingStyle: "utf8",
        replacements: [[1, 1, ["new"]]],
      },
    ]]));

    expect(files.get("docs/test.md")?.content).toBe(text);
    expect(calls.map((call) => call.operation)).toEqual(["read-bulk-b64", "apply-replacements-bulk-stdin"]);
    expect(calls[0]?.args).toEqual(["docs/test.md"]);
    expect(calls[1]?.args).toEqual(["1"]);
  });
});

describe("ssh WSL mounted workspace ownership", () => {
  test("bridges the mounted workspace owner to Git for direct and child shell commands", () => {
    const direct = parseSshInvocation("G14-WSL:/mnt/d/Work/demo", ["git", "rev-parse", "--show-toplevel"]);
    const childShell = parseSshInvocation("G14-WSL:/mnt/d/Work/demo", ["bash", "--", "git status --short"]);

    for (const invocation of [direct, childShell]) {
      expect(invocation.parsed.remoteCommand).toContain("UNIDESK_TRANS_HOST_WORKSPACE_OWNER_UID");
      expect(invocation.parsed.remoteCommand).toContain('export SUDO_UID="$UNIDESK_TRANS_HOST_WORKSPACE_OWNER_UID"');
      expect(invocation.parsed.remoteCommand).toContain('stat -c %u .');
    }
  });

  test("does not change ownership trust for ordinary Linux workspaces", () => {
    const invocation = parseSshInvocation("D601:/home/ubuntu/workspace/demo", ["git", "status", "--short"]);

    expect(invocation.parsed.remoteCommand).not.toContain("UNIDESK_TRANS_HOST_WORKSPACE_OWNER_UID");
    expect(invocation.parsed.remoteCommand).not.toContain("SUDO_UID");
  });
});

describe("ssh stdout bounded streaming", () => {
  test("uses YAML output policy defaults and clamps env override", () => {
    const policy = readCliOutputPolicy();
    expect(sshStdoutStreamMaxBytes({} as NodeJS.ProcessEnv)).toBe(policy.maxStdoutBytes);
    expect(sshStderrStreamMaxBytes({} as NodeJS.ProcessEnv)).toBe(policy.maxStdoutBytes);
    expect(sshStdoutStreamMaxBytes({ UNIDESK_SSH_STDOUT_STREAM_MAX_BYTES: "8192" } as NodeJS.ProcessEnv)).toBe(8192);
    expect(sshStderrStreamMaxBytes({ UNIDESK_SSH_STDERR_STREAM_MAX_BYTES: "8192" } as NodeJS.ProcessEnv)).toBe(8192);
    expect(sshStdoutStreamMaxBytes({ UNIDESK_SSH_STDOUT_STREAM_MAX_BYTES: "1" } as NodeJS.ProcessEnv)).toBe(4096);
    expect(sshStdoutStreamMaxBytes({ UNIDESK_SSH_STDOUT_STREAM_MAX_BYTES: String(64 * 1024 * 1024) } as NodeJS.ProcessEnv)).toBe(16 * 1024 * 1024);
  });

  test("formats truncation hint without echoing remote command", () => {
    const invocation = parseSshInvocation("D601:win/c/test", ["ps"]);
    expect(invocation.parsed.remoteCommand).not.toBeNull();
    const hint = sshStdoutTruncationHint({
      invocation,
      transport: "backend-core-broker",
      thresholdBytes: 4096,
      observedBytesAtTruncation: 5000,
      dumpPath: "/tmp/unidesk-cli-output/sample.stdout.bin",
    });
    const formatted = formatSshStdoutTruncationHint(hint);

    expect(formatted.startsWith("UNIDESK_SSH_STDOUT_TRUNCATED ")).toBe(true);
    const payload = JSON.parse(formatted.slice("UNIDESK_SSH_STDOUT_TRUNCATED ".length)) as Record<string, unknown>;
    expect(payload.code).toBe("ssh-stdout-truncated");
    expect(payload.providerId).toBe("D601");
    expect(payload.route).toBe("D601:win/c/test");
    expect(payload.thresholdBytes).toBe(4096);
    expect(payload.disclosurePolicy).toMatchObject({
      name: "bounded-stream-explicit-dump",
      configPath: "config/unidesk-cli.yaml",
    });
    expect(formatted).not.toContain("Get-Content");
  });

  test("forwarder bounds stdout without creating a default dump", () => {
    const invocation = parseSshInvocation("D601:win/c/test", ["ps"]);
    const forwarded: Buffer[] = [];
    const stdout = {
      write(chunk: string | Buffer): boolean {
        forwarded.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
        return true;
      },
    } as NodeJS.WritableStream;
    const forwarder = createSshStdoutForwarder({
      invocation,
      transport: "frontend-websocket",
      maxBytes: 5,
      stdout,
    });

    expect(forwarder.write(Buffer.from("abc"))).toBeNull();
    const hint = forwarder.write(Buffer.from("defgh"));
    expect(hint).not.toBeNull();
    expect(forwarder.write(Buffer.from("ijkl"))).toBeNull();
    expect(Buffer.concat(forwarded).toString("utf8")).toBe("abcde");
    expect(hint).toContain("UNIDESK_SSH_STDOUT_TRUNCATED");
    expect(hint).toContain("\"transport\":\"frontend-websocket\"");
    expect(hint).toContain("\"forwardedBytes\":5");
    const payload = JSON.parse(hint!.slice("UNIDESK_SSH_STDOUT_TRUNCATED ".length)) as { dumpPath: string | null; action: string };
    expect(payload.dumpPath).toBeNull();
    expect(payload.action).toContain("cat/head/tail/rg");
    const summary = sshTruncationCompletionSummary({
      invocation,
      transport: "frontend-websocket",
      exitCode: 0,
      timedOut: false,
      startedAtMs: Date.now() - 1234,
      stdout: forwarder.summary(),
      stderr: null,
    });
    const formattedSummary = formatSshTruncationCompletionSummary(summary);
    expect(formattedSummary).toContain("UNIDESK_SSH_TRUNCATION_SUMMARY");
    expect(formattedSummary).toContain("\"exitCode\":0");
    expect(formattedSummary).toContain("\"commandOmitted\":true");
    expect(formattedSummary).toContain("\"dumpPath\":null");
  });

  test("keeps complete stream capture behind an explicit audited switch", () => {
    expect(sshStreamDumpExplicitlyRequested({} as NodeJS.ProcessEnv)).toBe(false);
    expect(sshStreamDumpExplicitlyRequested({ UNIDESK_TRAN_STREAM_DUMP: "1" } as NodeJS.ProcessEnv)).toBe(true);
    const invocation = parseSshInvocation("D601:win/c/test", ["ps"]);
    const stdout = { write(): boolean { return true; } } as NodeJS.WritableStream;
    const forwarder = createSshStdoutForwarder({
      invocation,
      transport: "frontend-websocket",
      maxBytes: 5,
      stdout,
      persistDump: true,
    });
    expect(forwarder.write(Buffer.from("abc"))).toBeNull();
    const hint = forwarder.write(Buffer.from("defgh"));
    forwarder.write(Buffer.from("ijkl"));
    const payload = JSON.parse(hint!.slice("UNIDESK_SSH_STDOUT_TRUNCATED ".length)) as { dumpPath: string };
    expect(readFileSync(payload.dumpPath, "utf8")).toBe("abcdefghijkl");
    rmSync(payload.dumpPath, { force: true });
  });

  test("stderr forwarder uses the same dump guard and marker", () => {
    const invocation = parseSshInvocation("D601:win/c/test", ["ps"]);
    const forwarded: Buffer[] = [];
    const stderr = {
      write(chunk: string | Buffer): boolean {
        forwarded.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
        return true;
      },
    } as NodeJS.WritableStream;
    const forwarder = createSshStderrForwarder({
      invocation,
      transport: "backend-core-broker",
      maxBytes: 4,
      stderr,
    });

    const hint = forwarder.write(Buffer.from("abcdef"));
    expect(Buffer.concat(forwarded).toString("utf8")).toBe("abcd");
    expect(hint).toContain("UNIDESK_SSH_STDERR_TRUNCATED");
    expect(hint).toContain("\"stream\":\"stderr\"");
  });
});

describe("ssh backend selection", () => {
  test("uses trans YAML backend-core preference without runtime docker detection", () => {
    const dir = mkdtempSync(join(tmpdir(), "unidesk-trans-config-"));
    const configPath = join(dir, "trans.ymal");
    writeFileSync(configPath, [
      "version: 1",
      "kind: UniDeskTransConfig",
      "ssh:",
      "  backend: backend-core",
      "",
    ].join("\n"));
    try {
      const plan = sshCaptureBackendPlan(minimalConfig(), { UNIDESK_TRANS_CONFIG_PATH: configPath } as NodeJS.ProcessEnv);

      expect(plan.backend).toBe("local-backend-core-broker");
      expect(plan.remoteHost).toBeNull();
      expect(plan.reason).toBe(`configured:${configPath}#ssh.backend`);
      expect(plan.localBackendCore.backendCoreContainer).toBe(true);
      expect(plan.localBackendCore.source).toBe(`${configPath}#ssh.backend`);
    } finally {
      rmSync(dir, { recursive: true, force: true });
    }
  });

  test("uses trans YAML frontend websocket preference when configured", () => {
    const dir = mkdtempSync(join(tmpdir(), "unidesk-trans-config-"));
    const configPath = join(dir, "trans.ymal");
    writeFileSync(configPath, [
      "version: 1",
      "kind: UniDeskTransConfig",
      "ssh:",
      "  backend: frontend-websocket",
      "",
    ].join("\n"));
    try {
      const plan = sshCaptureBackendPlan(minimalConfig("198.51.100.4"), {
        UNIDESK_TRANS_CONFIG_PATH: configPath,
        UNIDESK_MAIN_SERVER_IP: "198.51.100.4",
      } as NodeJS.ProcessEnv);

      expect(plan.backend).toBe("remote-frontend-websocket");
      expect(plan.remoteHost).toBe("198.51.100.4");
      expect(plan.reason).toBe(`configured:${configPath}#ssh.backend`);
    } finally {
      rmSync(dir, { recursive: true, force: true });
    }
  });
});

describe("ssh removed shell aliases", () => {
  test("reports route-aware replacement examples for trans script", () => {
    const previousEntrypoint = process.env.UNIDESK_SSH_ENTRYPOINT;
    process.env.UNIDESK_SSH_ENTRYPOINT = "trans";
    try {
      parseSshInvocation("D601:/tmp", ["script", "--", "pwd"]);
      throw new Error("expected script alias to fail");
    } catch (error) {
      expect(error).toBeInstanceOf(Error);
      const payload = error as Error & {
        code?: string;
        entrypoint?: string;
        route?: string;
        operation?: string;
        replacementExamples?: {
          posixSh?: string;
          bash?: string;
        };
      };
      expect(payload.code).toBe("ssh-removed-shell-alias");
      expect(payload.entrypoint).toBe("trans");
      expect(payload.route).toBe("D601:/tmp");
      expect(payload.operation).toBe("script");
      expect(payload.replacementExamples?.posixSh).toBe("trans D601:/tmp sh -- 'pwd'");
      expect(payload.replacementExamples?.bash).toBe("trans D601:/tmp bash -- 'pwd'");
    } finally {
      if (previousEntrypoint === undefined) {
        delete process.env.UNIDESK_SSH_ENTRYPOINT;
      } else {
        process.env.UNIDESK_SSH_ENTRYPOINT = previousEntrypoint;
      }
    }
  });
});
