import assert from "node:assert/strict";
import test from "node:test";
import { parseRemotePathProbe, probeRemotePath } from "../src/remote-probe.ts";
import type { RemoteExecOptions, RemoteTransport } from "../src/ssh2-transport.ts";

class ProbeTransport implements RemoteTransport {
  command = "";
  cwd = "";
  connect(): Promise<void> { return Promise.resolve(); }
  dispose(): Promise<void> { return Promise.resolve(); }
  exec(command: string, cwd: string, options: RemoteExecOptions): Promise<{ exitCode: number | null }> {
    this.command = command;
    this.cwd = cwd;
    const start = command.match(/'(__PI_SSH_PROBE_[a-f0-9]+_START__)'/)?.[1];
    const end = command.match(/'(__PI_SSH_PROBE_[a-f0-9]+_END__)'/)?.[1];
    if (!start || !end) throw new Error("probe markers missing");
    options.onData(Buffer.from(`login banner\n${start}/srv/project${end}\nlogout banner\n`));
    return Promise.resolve({ exitCode: 0 });
  }
  capture(): Promise<{ exitCode: number | null; output: Buffer }> { throw new Error("not used"); }
  readFile(): Promise<Buffer> { throw new Error("not used"); }
  ensureReadable(): Promise<void> { throw new Error("not used"); }
  ensureReadableWritable(): Promise<void> { throw new Error("not used"); }
  detectImageMimeType(): Promise<string | null> { throw new Error("not used"); }
  mkdir(): Promise<void> { throw new Error("not used"); }
  writeFile(): Promise<void> { throw new Error("not used"); }
}

test("extracts one framed absolute path while ignoring shell startup output", async () => {
  const transport = new ProbeTransport();
  assert.equal(await probeRemotePath(transport, "cwd", "/srv"), "/srv/project");
  assert.equal(transport.cwd, "/srv");
  assert.match(transport.command, /pwd -P/);
});

test("rejects unframed, relative, multiline, and oversized path probes", () => {
  assert.throws(() => parseRemotePathProbe(Buffer.from("/srv"), "START", "END", "cwd"), /invalid framed/);
  assert.throws(() => parseRemotePathProbe(Buffer.from("STARTrelativeEND"), "START", "END", "cwd"), /absolute POSIX path/);
  assert.throws(() => parseRemotePathProbe(Buffer.from("START/srv\notherEND"), "START", "END", "cwd"), /absolute POSIX path/);
  assert.throws(
    () => parseRemotePathProbe(Buffer.alloc(64 * 1024 + 1), "START", "END", "cwd"),
    /exceeded 65536 bytes/,
  );
});

test("forwards cancellation to the probe exec call", async () => {
  const controller = new AbortController();
  controller.abort();
  const transport = new ProbeTransport();
  const original = transport.exec.bind(transport);
  transport.exec = (command, cwd, options) => {
    assert.equal(options.signal, controller.signal);
    if (options.signal?.aborted) return Promise.reject(new Error("SSH command aborted"));
    return original(command, cwd, options);
  };
  await assert.rejects(probeRemotePath(transport, "home", ".", controller.signal), /SSH command aborted/);
});
