var _ = require('underscore');
var selftest = require('../tool-testing/selftest.js');
var Run = selftest.Run;
var testUtils = require('../tool-testing/test-utils.js');
var utils = require('../utils/utils.js');
var authClient = require('../meteor-services/auth-client.js');
var auth = require('../meteor-services/auth.js');

// Run curl with the given specifications. Return an instance of Run.
var runCurl = function (/*args*/) {
  var curl = "/usr/bin/curl";
  return new Run(curl, {
    args: _.toArray(arguments)
  });
};

// Some constants.
var GALAXY_USERNAME = process.env.GALAXY_USERNAME;
var GALAXY_PASSWORD = process.env.GALAXY_PASSWORD;
var GALAXY_URL = process.env.DEPLOY_HOSTNAME;
var GALAXY_MOCK_MODE = process.env.GALAXY_MOCK_MODE;

// Fail if the test is obviously not set up for using Galaxy.
//
// Make sure that we have at least set all of the variables that we need to run
// against a Galaxy (GALAXY_URL, username & password). An extra safety
// check to avoid strange errors, deploying/calling random methods on Mother,
// etc.
exports.sanityCheck = selftest.markStack(function () {
  if (! GALAXY_URL ) {
    selftest.fail("Please specify a GALAXY_URL to test against Galaxy.\n");
  }
  if (! GALAXY_USERNAME ||
      ! GALAXY_PASSWORD) {
    selftest.fail(
      "Can't use test account with Galaxy. " +
       "Please specify GALAXY_USERNAME and GALAXY_PASSWORD.\n");
  }
  if (! process.env.APP_MONGO) {
    selftest.fail(
      "Please provide an APP_MONGO url to use for deployed apps.\n");
   }
});

// Login to Galaxy with environment-variable credentials passed in by the user.
//
// Unlike the normal `meteor deploy` Galaxy is not yet publicly available, so
// we don't want to use the publicly-accessible test account here.
exports.loginToGalaxy = selftest.markStack(async function (sandbox) {
  var user = GALAXY_USERNAME;
  var pass = GALAXY_PASSWORD;
  await testUtils.login(sandbox, user, pass);
});

// Curl an app running on Galaxy. Automatically follow redirects.
//
// Dealing with DNS on Galaxy can be complicated. The standard way to ensure
// that we hit the right app on the right Galaxy is to curl the galaxy origin,
// with the host header set to our query app.
exports.curlToGalaxy = selftest.markStack(function (url) {
  var hostHeader = "host: " + url;
  var galaxyOrigin = GALAXY_URL;
  return runCurl("-vLH", hostHeader, galaxyOrigin);
});

// String we expect to hit on 200 OK.
exports.httpOK = "HTTP/1.1 200 OK";

// String we expect to hit when using SSL.
exports.httpRedirect = "HTTP/1.1 307 Temporary Redirect";

// We expect containers to take some time to startup.
//
// In the future, we can use this function to poll whether the containers have
// started and maybe our tests will be faster.
exports.waitForContainers = selftest.markStack(async function () {
  // We are not spinning up any containers in mock mode, so don't wait too long.
  var waitTime = GALAXY_MOCK_MODE ? 1000 : 1000 * 10 * utils.timeoutScaleFactor;
  await utils.sleepMs(waitTime);
});

// Deploy an app against a Galaxy
//
// When we deploy to Galaxy, we need to specify a Mongo URL and wait a little
// longer for the app to spin up.
//
// Options:
//   - settings: app settings, NOT including mandatory galaxy settings
//     such as MONGO_URL
//   - appName: app name to use; will be generated randomly if not
//     provided
//   - templateApp: the name of the template app to use. defaults to
//    'simple-app'
//   - useOldSettings: don't make a new settings object this app! This is a
//     redeploy, so reuse the settings that Galaxy has saved.
//
exports.createAndDeployApp =  selftest.markStack(async function (sandbox, options) {
  options = options || {};
  var settings = options.settings;
  var appName = options.appName || testUtils.randomAppName();

  // The simple app contains standard app packages and some small bits of code
  // so that we can check that it is being served correctly. Let's use that as
  // our default.
  var templateApp = options.templateApp || 'simple-app';

  // Create the new galaxy settings.
  var galaxySettings = {
    "galaxy.meteor.com" : {
      env: {
        // XXX: Right now, all the galaxy test apps use the same mongo. This is
        // actually kind of super awkward... but generating and destroying new DBs
        // seems like it is introducing a bit too much complexity at this stage.
        "MONGO_URL": process.env.APP_MONGO
      }
    }
  };

  var fullAppName;
  if (! options.useOldSettings) {
    // Add all the settings together and write them out. Let user settings
    // override ours.
    var allSettings = Object.assign(galaxySettings, settings);
    var settingsFile = "settings-" + appName + ".json";
    sandbox.write(settingsFile, JSON.stringify(allSettings));

    fullAppName = testUtils.createAndDeployApp(sandbox, {
      settingsFile: "../" + settingsFile,
      appName: appName,
      templateApp: templateApp
    });
  } else {
    fullAppName = testUtils.createAndDeployApp(sandbox, {
      appName: appName,
      templateApp: templateApp
    });
  }

  // Galaxy might take a while to spin up an app.
  await exports.waitForContainers();

  return fullAppName;

});

// Cleanup the app by deleting it from Galaxy.
//
// XXX: We should also clean out its Mongo, but we don't, since, currently, none
// of our apps actually put any records into it.
exports.cleanUpApp = selftest.markStack(async function (sandbox, appName) {
  await testUtils.cleanUpApp(sandbox, appName);

  // Galaxy might take a while to spin up an app, though it should be fairly
  // quick.
  await exports.waitForContainers();
});

//////////////////////////////////////////////////////////////////////////////
//  We want to test some of the server-side functionality that doesn't actually
//  have a command-line API right now. Below functionas are going to use the
//  tool's ability to make a DDP client and connect to the server to call
//  methods directly.
////////////////////////////////////////////////////////////////////////////////

// Returns a logged in connection to GalaxyAPI
exports.loggedInGalaxyAPIConnection = selftest.markStack(async function () {
  // The credentials of the user might not be the credentials of the galaxytester.
  await auth.doInteractivePasswordLogin({
    username: GALAXY_USERNAME,
    password: GALAXY_PASSWORD
  });
  var galaxyDomain = GALAXY_URL;
  var galaxyUrl = (GALAXY_MOCK_MODE ? "http://" : "https://") + galaxyDomain;
  return authClient.loggedInConnection(
    galaxyUrl,
    galaxyDomain,
    "galaxy-api"
  );
});

// If the connection has disconnected, close it and open a new one. (workaround
// for the fact that connections in the tool do not reconnect)
exports.renewConnection = selftest.markStack(async function (conn) {
  if (!conn.connected) {
    conn.close();
    conn = await exports.loggedInGalaxyAPIConnection();
  }
  return conn;
});

// Given a connection, makes a call to Galaxy API.
exports.callGalaxyAPI = function (conn, ...args) {
  conn = exports.renewConnection(conn);
  return conn.callAsync(...args);
};

// Gets app record from Galaxy API by name.
//
// This method will create and manage its own connection.
exports.getAppRecordByName = selftest.markStack(async function (appName) {
  var conn = await exports.loggedInGalaxyAPIConnection();
  var appRecord = {};
  await conn.connection.registerStoreServer('app', {
    update: function (msg) {
      if (msg.msg === 'added' && msg.fields &&
          msg.fields.hostname === appName) {
        appRecord = Object.assign({ _id: msg.id }, msg.fields);
      }
    }
  });
  await conn.subscribeAndWait("/app", appName);
  // If we can't find the app, fail the test right now.
  if (_.isEmpty(appRecord)) {
    selftest.fail("Cannot find app: ", appName);
  }
  conn.close();
  return appRecord;
});


// Get container statuses for the given app ID.
//
// This method will create and manage its own connection.
exports.getAppContainerStatuses = selftest.markStack(async function (appId, appName) {
  var conn = await exports.loggedInGalaxyAPIConnection();

  var containers = [];
  var statuses = "/app/containerStatuses";
  await conn.connection.registerStoreServer(statuses, {
    update: function (msg) {
      if (msg.msg === 'added' && msg.fields &&
          msg.fields.appId === appId) {
        containers.push(Object.assign({ _id: msg.id }, msg.fields));
      }
    }
  });
  await conn.subscribeAndWait(statuses, appName);
  conn.close();
  return containers;
});

// Close and logout.
exports.closeGalaxyConnection = selftest.markStack(async function (conn) {
  await auth.logoutCommand();
  conn.close();
});


// Ignore HTTP checks in mock mode
exports.ignoreHttpChecks = selftest.markStack(function () {
  return !! GALAXY_MOCK_MODE;
});
