import { NodeSSH, type SSHExecCommandResponse } from "node-ssh";
import { prisma } from "./db";
import { decrypt } from "./crypto";

export type ExecResult = {
  stdout: string;
  stderr: string;
  code: number | null;
  ok: boolean;
};

export async function connectHost(hostId: string): Promise<NodeSSH> {
  const h = await prisma.host.findUnique({ where: { id: hostId } });
  if (!h) throw new Error(`host_not_found:${hostId}`);

  const ssh = new NodeSSH();
  const opts: Parameters<NodeSSH["connect"]>[0] = {
    host: h.host,
    port: h.port,
    username: h.username,
    readyTimeout: 15_000,
    keepaliveInterval: 10_000,
  };
  if (h.authType === "key" && h.privateKeyEnc) {
    opts.privateKey = decrypt(h.privateKeyEnc);
  } else if (h.passwordEnc) {
    opts.password = decrypt(h.passwordEnc);
  } else {
    throw new Error(`host_no_auth:${hostId}`);
  }
  await ssh.connect(opts);
  return ssh;
}

export async function exec(
  hostId: string,
  command: string,
  opts: { cwd?: string; timeoutMs?: number } = {},
): Promise<ExecResult> {
  const ssh = await connectHost(hostId);
  try {
    const r: SSHExecCommandResponse = await Promise.race([
      ssh.execCommand(command, { cwd: opts.cwd }),
      new Promise<SSHExecCommandResponse>((_, reject) =>
        setTimeout(() => reject(new Error("ssh_timeout")), opts.timeoutMs ?? 60_000),
      ),
    ]);
    return {
      stdout: r.stdout || "",
      stderr: r.stderr || "",
      code: typeof r.code === "number" ? r.code : null,
      ok: r.code === 0,
    };
  } finally {
    ssh.dispose();
  }
}

// Tiện ích chạy nhiều lệnh và tổng hợp log.
export async function execScript(
  hostId: string,
  steps: { label: string; cmd: string }[],
  opts: { stopOnFail?: boolean; timeoutMsPerStep?: number } = {},
): Promise<{ ok: boolean; log: string; lastCode: number | null }> {
  const stop = opts.stopOnFail ?? true;
  const lines: string[] = [];
  let lastCode: number | null = 0;
  let ok = true;
  for (const s of steps) {
    lines.push(`==> ${s.label}`);
    lines.push(`$ ${s.cmd}`);
    const r = await exec(hostId, s.cmd, { timeoutMs: opts.timeoutMsPerStep });
    if (r.stdout) lines.push(r.stdout.trimEnd());
    if (r.stderr) lines.push(r.stderr.trimEnd());
    lines.push(`(exit ${r.code})`);
    lastCode = r.code;
    if (!r.ok) {
      ok = false;
      if (stop) break;
    }
  }
  return { ok, log: lines.join("\n"), lastCode };
}

export async function ping(hostId: string): Promise<ExecResult> {
  return exec(hostId, "uname -a && uptime", { timeoutMs: 10_000 });
}
