const { spawn } = require('child_process');
const paths = require('./paths');

/**
 * @param {string} ip
 * @param {string} command
 * @param {function} [onData]  — streaming callback
 * @param {object}  [opts]
 * @param {number}  [opts.timeout]  — kill after N ms (default 300000 = 5 min)
 * @param {boolean} [opts.tty]      — force pseudo-terminal (-tt)
 * @param {string}  [opts.stdin]    — data to write to stdin then close
 */
function run(ip, command, onData, opts = {}) {
  return new Promise((resolve, reject) => {
    let keyPath;
    try {
      keyPath = paths.getSshIdentityPath();
    } catch (err) {
      reject(err);
      return;
    }
    const timeout = opts.timeout || 300000;

    const args = [
      '-o', 'StrictHostKeyChecking=no',
      '-o', 'LogLevel=QUIET',
      '-o', 'ConnectTimeout=10',
      '-o', 'ServerAliveInterval=15',
      '-o', 'ServerAliveCountMax=4',
    ];

    if (opts.tty) args.push('-tt');

    args.push('-i', keyPath, `dune@${ip}`, command);

    const needsStdinPipe = opts.tty || opts.stdin != null;

    const proc = spawn('ssh', args, {
      windowsHide: true,
      stdio: [needsStdinPipe ? 'pipe' : 'ignore', 'pipe', 'pipe'],
    });

    if (needsStdinPipe) {
      if (opts.stdin != null) proc.stdin.write(opts.stdin);
      proc.stdin.end();
    }

    let stdout = '';
    let stderr = '';
    let killed = false;

    const timer = setTimeout(() => {
      killed = true;
      proc.kill('SIGKILL');
    }, timeout);

    proc.stdout.on('data', (chunk) => {
      const text = chunk.toString();
      stdout += text;
      if (onData) onData(text);
    });

    proc.stderr.on('data', (chunk) => {
      const text = chunk.toString();
      stderr += text;
      if (onData) onData(text);
    });

    proc.on('error', (err) => { clearTimeout(timer); reject(err); });
    proc.on('close', (code) => {
      clearTimeout(timer);
      if (killed) {
        const err = new Error(`SSH command timed out after ${timeout / 1000}s`);
        err.stdout = stdout;
        err.stderr = stderr;
        return reject(err);
      }
      if (code === 0) {
        resolve(stdout.trim());
      } else {
        const stderrTrim = stderr.trim();
        const stdoutTrim = stdout.trim();
        const ptyClosed = /Connection to .+ closed\.?$/m.test(stderrTrim);
        if (stdoutTrim && (!stderrTrim || ptyClosed)) {
          resolve(stdoutTrim);
          return;
        }
        const err = new Error(stderrTrim || stdoutTrim || `SSH exited with code ${code}`);
        err.code = code;
        err.stdout = stdout;
        err.stderr = stderr;
        reject(err);
      }
    });
  });
}

module.exports = { run, getKeyPath: paths.getKeyPath, sshKeyExists: paths.sshKeyExists };
