'use strict';

//var fs = require('fs');
var os = require('os');
var mkdirp = require('mkdirp');
var exec = require('child_process').exec;
var path = require('path');

var Launcher = module.exports;
Launcher._killAll = function (fn) {
  var psList = require('ps-list');
  psList().then(function (procs) {
    procs.forEach(function (proc) {
      if ('node' === proc.name && /\btelebitd\b/i.test(proc.cmd)) {
        console.log(proc);
        process.kill(proc.pid);
        return true;
      }
    });
    // Two things:
    // 1) wait to see if the process dies
    // 2) wait to give time for the socket to connect
    setTimeout(function () {
      if (fn) { fn(null); return; }
    }, 1.75 * 1000);
  });
};
Launcher._getError = function getError(err, stderr) {
  if (err) { return err; }
  if (stderr) {
    err = new Error(stderr);
    err.code = 'ELAUNCHER';
    return err;
  }
};
Launcher._detect = function (things, fn) {
  if (things.launcher) {
    if ('string' === typeof things.launcher) {
      fn(null, things.launcher);
      return;
    }
    if ('function' === typeof things.launcher) {
      things.launcher(things);
      return;
    }
  }

  // could have used "command-exists" but I'm trying to stay low-dependency
  // os.platform(), os.type()
  if (!/^win/i.test(os.platform())) {
    if (/^darwin/i.test(os.platform())) {
      exec('command -v launchctl', things._execOpts, function (err, stdout, stderr) {
        err = Launcher._getError(err, stderr);
        fn(err, 'launchctl');
      });
    } else {
      exec('command -v systemctl', things._execOpts, function (err, stdout, stderr) {
        err = Launcher._getError(err, stderr);
        fn(err, 'systemctl');
      });
    }
  } else {
    // https://stackoverflow.com/questions/17908789/how-to-add-an-item-to-registry-to-run-at-startup-without-uac
    // wininit? regedit? SCM?
    // REG ADD "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /V "My App" /t REG_SZ /F /D "C:\MyAppPath\MyApp.exe"
    // https://www.microsoft.com/developerblog/2015/11/09/reading-and-writing-to-the-windows-registry-in-process-from-node-js/
    // https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/reg-add
    // https://social.msdn.microsoft.com/Forums/en-US/5b318f44-281e-4098-8dee-3ba8435fa391/add-registry-key-for-autostart-of-app-in-ice?forum=quebectools
    // utils.elevate
    // https://github.com/CatalystCode/windows-registry-node
    exec('where reg.exe', things._execOpts, function (err, stdout, stderr) {
      //console.log((stdout||'').trim());
      if (stderr) {
        console.error(stderr);
      }
      fn(err, 'reg.exe');
    });
  }
};
Launcher.install = function (things, fn) {
  if (!fn) { fn = function (err) { if (err) { console.error(err); } }; }
  things = things || {};
  // in some future version we can take this file out
  // and accept process.env from things
  var installLauncher = require('./template-launcher');

  // Right now this is just for npm install -g and npx
  if (things.env) {
    things.env.PATH = things.env.PATH || process.env.PATH;
  } else {
    things.env = process.env;
  }
  things.argv = things.argv || process.argv;
  things._execOpts = { windowsHide: true, env: things.env };
  var telebitRoot = path.join(__dirname, '../..');
  var vars = {
    telebitPath: telebitRoot
  , telebitUser: os.userInfo().username
  , telebitGroup: (/^darwin/i.test(os.platform()) ? 'staff' : os.userInfo().username)
  , telebitRwDirs: [
      telebitRoot
    , path.join(os.homedir(), '.config/telebit')
    , path.join(os.homedir(), '.local/share/telebit')
    ]
  , telebitNode: (things.argv[0]||'').replace(/\.exe/i, '') // path.join(telebitRoot, 'bin/node')
  , telebitBin: path.join(telebitRoot, 'bin/telebit')
  , telebitdBin: path.join(telebitRoot, 'bin/telebitd')
  , telebitJs: path.join(telebitRoot, 'bin/telebit.js')
  , telebitdJs: path.join(telebitRoot, 'bin/telebitd.js')
  , telebitConfig: path.join(os.homedir(), '.config/telebit/telebit.yml')
  , telebitdConfig: path.join(os.homedir(), '.config/telebit/telebitd.yml')
  , TELEBIT_LOG_DIR: path.join(os.homedir(), '.local/share/telebit/var/log')
  , TELEBIT_SOCK_DIR: path.join(os.homedir(), '.local/share/telebit/var/run')
  };
  vars.telebitBinTpl = path.join(telebitRoot, 'usr/share/dist/bin/telebit.tpl');
  vars.telebitNpm = path.resolve(vars.telebitNode, '../npm');
  vars.nodePath = path.resolve(vars.telebitNode, '../../lib/node_modules');
  vars.npmConfigPrefix = path.resolve(vars.telebitNode, '..', '..');
  vars.userspace = (!things.telebitUser || (things.telebitUser === os.userInfo().username)) ? true : false;
  if (-1 === vars.telebitRwDirs.indexOf(vars.npmConfigPrefix)) {
    vars.telebitRwDirs.push(vars.npmConfigPrefix);
  }
  vars.telebitRwDirs = vars.telebitRwDirs.join(' ');
  var launchers = {
    'node': function () {
      var fs = require('fs');
      var spawn = require('child_process').spawn;
      var logpath = path.join(os.homedir(), '.local/share/telebit/var/log');
      try {
        mkdirp.sync(logpath);
      } catch(e) {
        if (fn) { fn(e); return; }
        return;
      }
      var stdout = fs.openSync(path.join(logpath, 'info.log'), 'a');
      var stderr = fs.openSync(path.join(logpath, 'error.log'), 'a');

      var killed = 0;
      var err;
      var args = [
        path.join(telebitRoot, 'bin/telebitd.js')
      , 'daemon'
      , '--config'
      , vars.telebitdConfig
      ];
      var subprocess = spawn(
        vars.telebitNode
      , args
      , { detached: true
        , stdio: [ 'ignore', stdout, stderr ]
        }
      );
      //console.log('[debug]', vars.telebitNode, args.join(' '));
      subprocess.unref();
      subprocess.on('error', function (_err) {
        err = _err;
        killed += 1;
      });
      subprocess.on('exit', function (code, signal) {
        if (!err) { err = new Error('' + code + ' ' + signal + ' failure to launch'); }
        killed += 1;
      });

      // Two things:
      // 1) wait to see if the process dies
      // 2) wait to give time for the socket to connect
      setTimeout(function () {
        if (fn) { fn(err); return; }
      }, 1.75 * 1000);
      return;
    }
  , 'launchctl': function () {
      var launcher = path.join(os.homedir(), 'Library/LaunchAgents/cloud.telebit.remote.plist');
      try {
        mkdirp.sync(path.join(os.homedir(), 'Library/LaunchAgents'));
        installLauncher.sync({
            file: {
              tpl: vars.telebitBinTpl
            , launcher: path.join(vars.telebitPath, 'bin/telebit')
            , executable: true
          }
        , vars: vars
        });
        installLauncher({
          file: {
              tpl: path.join(vars.telebitPath, 'usr/share/dist/etc/skel/Library/LaunchAgents/cloud.telebit.remote.plist.tpl')
            , launcher: launcher
            }
          , vars: vars
        });
        var launcherstr = (vars.userspace ? "" : "sudo ") + "launchctl ";
        var execstr = launcherstr + "unload -w " + launcher;
        exec(execstr, things._execOpts, function (/*err, stdout, stderr*/) {
          // we probably only need to skip the stderr (saying that it can't stop something that isn't started)
          //err = Launcher._getError(err, stderr);
          //if (err) { fn(err); return; }
          //console.log((stdout||'').trim());
          //console.log('unload worked?');
          execstr = launcherstr + "load -w " + launcher;
          exec(execstr, things._execOpts, function (err, stdout, stderr) {
            err = Launcher._getError(err, stderr);
            if (err) { fn(err); return; }
            //console.log((stdout||'').trim());
            //console.log('load worked?');
            setTimeout(function () {
              fn(null);
            }, 1.25 * 1000);
          });
        });
      } catch(e) {
        console.error("'" + launcher + "' error:");
        console.error(e);
        if (fn) { fn(e); return; }
      }
    }
   , 'systemctl': function () {
      var launcher = path.join(os.homedir(), '.config/systemd/user/telebit.service');
      var launchername = 'telebit.service';
      try {
        mkdirp.sync(path.join(os.homedir(), '.config/systemd/user'));
        installLauncher({
          file: {
            tpl: path.join(vars.telebitPath, 'usr/share/dist/etc/skel/.config/systemd/user/telebit.service.tpl')
          , launcher: launcher
          }
        , vars: vars
        }, function () {
          // IMPORTANT
          // It's a dangerous to go alone, take this:
          // SYSTEMD_LOG_LEVEL=debug journalctl -xef --user-unit=telebit
          // (makes debugging systemd issues not "easy" per se, but possible)
          var launcherstr = (vars.userspace ? "" : "sudo ") + "systemctl " + (vars.userspace ? "--user " : "");
          var execstr = launcherstr + "daemon-reload";
          exec(execstr, things._execOpts, function (err, stdout, stderr) {
            err = Launcher._getError(err, stderr);
            if (err) { fn(err); return; }
            //console.log((stdout||'').trim());
            var execstr = launcherstr + "enable " + launchername;
            exec(execstr, things._execOpts, function (err, stdout, stderr) {
              err = Launcher._getError(err, stderr && !/Created symlink/i.test(stderr) && stderr || '');
              if (err) { fn(err); return; }
              //console.log((stdout||'').trim());
              var execstr = launcherstr + "restart " + launchername;
              exec(execstr, things._execOpts, function (err, stdout, stderr) {
                err = Launcher._getError(err, stderr);
                if (err) { fn(err); return; }
                //console.log((stdout||'').trim());
                setTimeout(function () {
                  var execstr = launcherstr + "status " + launchername;
                  exec(execstr, things._execOpts, function (err, stdout, stderr) {
                    err = Launcher._getError(err, stderr);
                    if (err) { fn(err); return; }
                    if (!/active.*running/i.test(stdout)) {
                      err = new Error("systemd failed to start '" + launchername + "'");
                    }
                    if (err) { fn(err); return; }
                    //console.log((stdout||'').trim());
                    fn(null);
                  });
                }, 1.25 * 1000);
              });
            });
          });
        });
      } catch(e) {
        console.error("'" + launcher + "' error:");
        console.error(e);
        if (fn) { fn(e); return; }
      }
    }
  , 'reg.exe': function () {
      if (!vars.userspace) {
        console.warn("sysetm-level, privileged services are not yet supported on windows");
      }
      vars.telebitNode += '.exe';
      var cmd = 'reg.exe add "HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"'
        + ' /V "Telebit" /t REG_SZ /D '
        + '"' + things.argv[0] + ' /c '  // something like C:\\Program Files (x64)\nodejs\node.exe
        + [ path.join(__dirname, 'bin/telebitd.js')
          , 'daemon'
          , '--config'
          , path.join(os.homedir(), '.config/telebit/telebitd.yml')
          ].join(' ')
        + '" /F'
        ;
      exec(cmd, things._execOpts, function (err, stdout, stderr) {
        err = Launcher._getError(err, stderr);
        if (err) { fn(err); return; }
        // need to start it for the first time ourselves
        run(null, 'node');
      });
    }
  };

  function run(err, launcher) {
    if (err) {
      console.error("No luck with '" + launcher + "', trying a child process instead...");
      console.error(err);
      launcher = 'node';
    }

    if (launchers[launcher]) {
      // console.log('Launching with launcher ' + launcher);
      mkdirp.sync(path.join(vars.telebitPath, 'bin'));
      mkdirp.sync(vars.TELEBIT_LOG_DIR);
      mkdirp.sync(vars.TELEBIT_SOCK_DIR);
      launchers[launcher]();
      return;
    } else {
      console.error("No launcher handler for '" + launcher+ "'");
    }
  }

  things._vars = vars;
  things._userspace = vars.userspace;
  Launcher._detect(things, run);
};
Launcher.uninstall = function (things, fn) {
  if (!fn) { fn = function (err) { if (err) { console.error(err); } }; }
  things = things || {};

  // Right now this is just for npm install -g and npx
  if (things.env) {
    things.env.PATH = things.env.PATH || process.env.PATH;
  } else {
    things.env = process.env;
  }
  things.argv = things.argv || process.argv;
  things._execOpts = { windowsHide: true, env: things.env };
  var vars = {
    telebitUser: os.userInfo().username
  };
  vars.userspace = (!things.telebitUser || (things.telebitUser === os.userInfo().username)) ? true : false;
  var launchers = {
    'node': function () {
      Launcher._killAll(fn);
    }
  , 'launchctl': function () {
      var launcher = path.join(os.homedir(), 'Library/LaunchAgents/cloud.telebit.remote.plist');
      try {
        var launcherstr = (vars.userspace ? "" : "sudo ") + "launchctl ";
        var execstr = launcherstr + "unload -w " + launcher;
        exec(execstr, things._execOpts, function (err, stdout, stderr) {
          // we probably only need to skip the stderr (saying that it can't stop something that isn't started)
          //err = Launcher._getError(err, stderr);
          //if (err) { fn(err); return; }
          //console.log((stdout||'').trim());
          //console.log('unload worked?');
          err = Launcher._getError(err, stderr);
          if (err) { fn(err); return; }
          //console.log((stdout||'').trim());
          //console.log('load worked?');
          setTimeout(function () {
            fn(null);
          }, 1.25 * 1000);
        });
      } catch(e) {
        console.error("'" + launcher + "' error (uninstall):");
        console.error(e);
        if (fn) { fn(e); return; }
      }
    }
   , 'systemctl': function () {
      var launcher = path.join(os.homedir(), '.config/systemd/user/telebit.service');
      var launchername = 'telebit.service';
      try {
        mkdirp.sync(path.join(os.homedir(), '.config/systemd/user'));
        // IMPORTANT
        // It's a dangerous to go alone, take this:
        // SYSTEMD_LOG_LEVEL=debug journalctl -xef --user-unit=telebit
        // (makes debugging systemd issues not "easy" per se, but possible)
        var launcherstr = (vars.userspace ? "" : "sudo ") + "systemctl " + (vars.userspace ? "--user " : "");
        var execstr = launcherstr + "disable " + launchername;
        exec(execstr, things._execOpts, function (err, stdout, stderr) {
          err = Launcher._getError(err, stderr && !/Removed symlink/i.test(stderr) && stderr || '');
          if (err) { fn(err); return; }
          //console.log((stdout||'').trim());
          var execstr = launcherstr + "stop " + launchername;
          exec(execstr, things._execOpts, function (err, stdout, stderr) {
            err = Launcher._getError(err, stderr);
            if (err) { fn(err); return; }
            //console.log((stdout||'').trim());
            setTimeout(function () {
              var execstr = launcherstr + "status " + launchername;
              exec(execstr, things._execOpts, function (err, stdout, stderr) {
                err = Launcher._getError(err, stderr);
                if (err) { fn(err); return; }
                if (!/inactive.*dead/i.test(stdout)) {
                  err = new Error("systemd failed to stop '" + launchername + "'");
                }
                if (err) { fn(err); return; }
                //console.log((stdout||'').trim());
                fn(null);
              });
            }, 1.25 * 1000);
          });
        });
      } catch(e) {
        console.error("'" + launcher + "' error:");
        console.error(e);
        if (fn) { fn(e); return; }
      }
    }
  , 'reg.exe': function () {
      if (!vars.userspace) {
        console.warn("sysetm-level, privileged services are not yet supported on windows");
      }
      var cmd = 'reg.exe add "HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"'
        + ' /V "Telebit" /F'
        ;
      exec(cmd, things._execOpts, function (err, stdout, stderr) {
        err = Launcher._getError(err, stderr);
        if (err) { fn(err); return; }
        // need to start it for the first time ourselves
        kill(null, 'node');
      });
    }
  };

  function kill(err, launcher) {
    if (err) {
      console.error("No luck with '" + launcher + "', trying a process.kill() instead...");
      console.error(err);
      launcher = 'node';
    }

    if (launchers[launcher]) {
      launchers[launcher]();
      return;
    } else {
      console.error("No launcher handler (uninstall) for '" + launcher + "'");
    }
  }

  things._vars = vars;
  things._userspace = vars.userspace;
  Launcher._detect(things, kill);
};

if (module === require.main) {
  Launcher.install({
    argv: process.argv
  , env: process.env
  }, function (err) {
    if (err) { console.error(err); return; }
    console.log("Telebit launched, or so it seems.");
  });
}