import { spawn, spawnSync } from "node:child_process";
import { closeSync, createWriteStream, existsSync, openSync, readSync, statSync } from "node:fs";

export interface CommandResult {
  command: string[];
  cwd: string;
  exitCode: number | null;
  stdout: string;
  stderr: string;
  signal: NodeJS.Signals | null;
  timedOut: boolean;
}

export function runCommand(command: string[], cwd: string, options: { timeoutMs?: number; env?: NodeJS.ProcessEnv } = {}): CommandResult {
  const result = spawnSync(command[0], command.slice(1), {
    cwd,
    encoding: "utf8",
    env: options.env,
    maxBuffer: 1024 * 1024 * 8,
    timeout: options.timeoutMs,
  });
  const error = result.error as (Error & { code?: string }) | undefined;
  const stderr = result.stderr ?? error?.message ?? "";
  return {
    command,
    cwd,
    exitCode: result.status,
    stdout: result.stdout ?? "",
    stderr,
    signal: result.signal,
    timedOut: error?.code === "ETIMEDOUT",
  };
}

export function commandOk(command: string[], cwd: string): boolean {
  return runCommand(command, cwd).exitCode === 0;
}

export async function runCommandToFiles(command: string[], cwd: string, stdoutFile: string, stderrFile: string): Promise<number | null> {
  const stdout = createWriteStream(stdoutFile, { flags: "a" });
  const stderr = createWriteStream(stderrFile, { flags: "a" });
  stdout.write(`$ ${command.map((part) => JSON.stringify(part)).join(" ")}\n`);
  const child = spawn(command[0], command.slice(1), { cwd, env: { ...process.env, UNIDESK_JOB_STDOUT_FILE: stdoutFile, UNIDESK_JOB_STDERR_FILE: stderrFile } });
  child.stdout.pipe(stdout, { end: false });
  child.stderr.pipe(stderr, { end: false });
  const exitCode = await new Promise<number | null>((resolve) => {
    child.on("close", (code) => resolve(code));
    child.on("error", (error) => {
      stderr.write(`${error.stack ?? error.message}\n`);
      resolve(127);
    });
  });
  stdout.write(`\n[exit_code=${exitCode}]\n`);
  stdout.end();
  stderr.end();
  return exitCode;
}

export function tailFile(path: string, maxBytes = 8192): string {
  if (!existsSync(path)) return "";
  const safeMaxBytes = Math.max(0, Math.floor(maxBytes));
  if (safeMaxBytes === 0) return "";
  const size = statSync(path).size;
  const bytesToRead = Math.min(size, safeMaxBytes);
  const buffer = Buffer.alloc(bytesToRead);
  const fd = openSync(path, "r");
  try {
    readSync(fd, buffer, 0, bytesToRead, size - bytesToRead);
  } finally {
    closeSync(fd);
  }
  return buffer.toString("utf8");
}
