import { randomBytes } from "node:crypto";
import { posix as posixPath } from "node:path";
import type { RemoteTransport } from "./ssh2-transport.ts";

const PROBE_TIMEOUT_SECONDS = 20;
const MAX_PROBE_OUTPUT_BYTES = 64 * 1024;

export type RemotePathProbe = "home" | "cwd";

function probeCommand(kind: RemotePathProbe, token: string): { command: string; start: string; end: string } {
  const start = `__PI_SSH_PROBE_${token}_START__`;
  const end = `__PI_SSH_PROBE_${token}_END__`;
  const assign = kind === "home"
    ? "pi_ssh_probe_value=$HOME"
    : "pi_ssh_probe_value=$(pwd -P) || exit $?";
  return {
    command: `${assign}\nprintf '%s%s%s' '${start}' "$pi_ssh_probe_value" '${end}'`,
    start,
    end,
  };
}

export function parseRemotePathProbe(output: Buffer, start: string, end: string, kind: RemotePathProbe): string {
  if (output.length > MAX_PROBE_OUTPUT_BYTES) {
    throw new Error(`remote ${kind} probe output exceeded ${MAX_PROBE_OUTPUT_BYTES} bytes`);
  }
  const text = output.toString("utf8");
  const startIndex = text.indexOf(start);
  const endIndex = startIndex < 0 ? -1 : text.indexOf(end, startIndex + start.length);
  if (startIndex < 0 || endIndex < 0 || text.indexOf(start, startIndex + start.length) >= 0) {
    throw new Error(`remote ${kind} probe returned an invalid framed response`);
  }
  const value = text.slice(startIndex + start.length, endIndex);
  if (!value.startsWith("/") || /[\0\r\n]/u.test(value)) {
    throw new Error(`remote ${kind} probe did not return one absolute POSIX path`);
  }
  return posixPath.normalize(value);
}

export async function probeRemotePath(
  transport: RemoteTransport,
  kind: RemotePathProbe,
  cwd = ".",
  signal?: AbortSignal,
): Promise<string> {
  const token = randomBytes(12).toString("hex");
  const probe = probeCommand(kind, token);
  const chunks: Buffer[] = [];
  let captured = 0;
  let overflow = false;
  const result = await transport.exec(probe.command, cwd, {
    signal,
    timeout: PROBE_TIMEOUT_SECONDS,
    onData(data) {
      if (captured + data.length > MAX_PROBE_OUTPUT_BYTES) {
        overflow = true;
        return;
      }
      chunks.push(data);
      captured += data.length;
    },
  });
  const output = Buffer.concat(chunks);
  if (overflow) throw new Error(`remote ${kind} probe output exceeded ${MAX_PROBE_OUTPUT_BYTES} bytes`);
  if (result.exitCode !== 0) {
    const detail = output.toString("utf8").replace(/\s+/gu, " ").trim().slice(0, 300);
    throw new Error(`remote ${kind} probe failed${result.exitCode === null ? "" : ` with exit code ${result.exitCode}`}${detail ? `: ${detail}` : ""}`);
  }
  return parseRemotePathProbe(output, probe.start, probe.end, kind);
}
