import { afterEach, describe, expect, test } from "bun:test";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { probeRemoteHosts } from "./remote";
import { defaultSshSpawn, remoteCommandArgv, resolveReachableSshAlias } from "./remote";

const dirs: string[] = [];
afterEach(async () => { await Promise.all(dirs.splice(0).map((dir) => rm(dir, { recursive: true }))); });

async function registryPath(): Promise<string> {
  const dir = await mkdtemp(join(tmpdir(), "remote-sessions-"));
  dirs.push(dir);
  const path = join(dir, "hosts.json");
  await writeFile(path, JSON.stringify({
    schema_version: 1,
    hosts: [
      { name: "local", state: "reachable", ssh_alias: "local-alias" },
      { name: "ok", state: "reachable", ssh_alias: "ok-alias" },
      { name: "down", state: "reachable", ssh_alias: "down-alias" },
      { name: "bad-json", state: "reachable", ssh_alias: "bad-json-alias" },
      { name: "bricked", state: "bricked", ssh_alias: "must-not-contact" },
    ],
  }));
  return path;
}

describe("probeRemoteHosts", () => {
  test("reports ok, ssh failure, invalid output, and registry-disabled hosts without contacting disabled hosts", async () => {
    const contacted: string[] = [];
    const result = await probeRemoteHosts({
      registryPath: await registryPath(),
      localHost: "local",
      now: () => Date.parse("2026-08-08T12:00:00.000Z"),
      execImpl: async (host, alias) => {
        contacted.push(`${host}:${alias}`);
        if (host === "down") throw new Error("ssh exit 255: connection refused\nignored");
        if (host === "bad-json") return "not json";
        return JSON.stringify([{ schemaVersion: 1, ledgerId: "remote-1", cwd: "/work", state: "ORPHANED", host: "wrong" }]);
      },
    });

    expect(result.find((probe) => probe.host === "ok")).toMatchObject({
      status: "ok", error: null, at: "2026-08-08T12:00:00.000Z",
      sessions: [{ id: "remote-1", host: "ok", state: "ORPHANED" }],
    });
    expect(result.find((probe) => probe.host === "down")).toMatchObject({ status: "failed", error: "ssh exit 255: connection refused" });
    expect(result.find((probe) => probe.host === "bad-json")?.status).toBe("failed");
    expect(result.find((probe) => probe.host === "bricked")).toMatchObject({ status: "not-reachable", error: "registry state: bricked" });
    expect(contacted).not.toContain("bricked:must-not-contact");
    expect(contacted).not.toContain("local:local-alias");
  });
});

describe("remote command execution", () => {
  test("quotes every remote command argument", () => {
    expect(remoteCommandArgv("box-alias", ["tmux", "-S", "/tmp/a b", "-t", "=it's"])).toEqual([
      "ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=4", "-o", "StrictHostKeyChecking=accept-new",
      "box-alias", "'tmux' '-S' '/tmp/a b' '-t' '=it'\\''s'",
    ]);
  });

  test("registry gating refuses unknown and unreachable hosts by name", async () => {
    const load = async () => ({
      schema_version: 1 as const, source: "test", orders: {},
      hosts: [{ name: "down", ssh_alias: "down-alias", state: "unreachable" as const, machine_id: null, roles: [], access: {}, rustdesk: null, notes: "" }],
    });
    expect(resolveReachableSshAlias("missing", load)).rejects.toThrow("missing is not listed");
    expect(resolveReachableSshAlias("down", load)).rejects.toThrow("down is not marked reachable");
  });

  test("a hung command is killed and returns an honest timeout", async () => {
    const result = await defaultSshSpawn([process.execPath, "-e", "setInterval(() => {}, 1000)"], 10);
    expect(result.rc).toBe(124);
    expect(result.stderr).toBe("ssh timed out after 0.01s");
  });
});
