
const gulp = require('gulp');
const spawn = require('cross-spawn').spawn;
const gutil = require('gulp-util');
const del = require('del');
const pm2 = require('pm2');
const path = require('path');
const fs = require('fs');
const Async = require('async');
const { flushRedis, getProcessConfig, getAppConfig } = require('../common');

/**
 * Process runtime arguments:
 * Use browsers option for specifying the browsers to run e2e tests with:
 *     gulp test-e2e --browsers firefox,chrome:headless
 *
 * If you want to debug by turning on the server without running the tests, do:
 *     gulp test-e2e --server-only
 * You need to manually turn off the server with ctrl+c.
 *
 *
 */

// Don't run e2e server with --no-server option
const MODE_NO_SERVER = isModeEnabled('no-server');
let MODE_SERVER_ONLY = isModeEnabled('server-only');
const MODE_SERVER_LOGS = isModeEnabled('server-logs');
const MODE_NO_DIST_BUILD = isModeEnabled('no-dist-build');
const MODE_SERVER_ONLY_DAEMON = isModeEnabled('server-only-daemon');

if (MODE_SERVER_ONLY_DAEMON) {
  MODE_SERVER_ONLY = true;
}

const testResultPath = 'shared/test/playwright/tests/playwright-screenshots';

function isModeEnabled(mode){
  return process.argv.indexOf('--' + mode) > -1;
}

gulp.task('run-e2e-server', function(done){

  // no daemon mode
  pm2.connect(true, function(err) {
    const config = getProcessConfig();
    // Stream logs before startup so startup logs display
    // Only capture logs from the target process
    // Don't allow server logs in daemon mode since you can just 'pm2 log' if you need them.
    // Not allowing server logs in daemon mode prevents process leaks.
    if(MODE_SERVER_LOGS && !MODE_SERVER_ONLY_DAEMON){
      pm2.streamLogs(config.name, 0, null, null, null);
    }

    // attempt to stop process if running
    pm2.delete(config.name, function(err) {
      // start the test server
      pm2.start(config, function(err, apps) {
        if (err) throw err;
      });
    });
  });

  // Detect if server is running and fail if it can't be accessed.
  const ServerChecker = require('../../shared/test/utils/ServerChecker');
  const testConfig = require('../../shared/test/playwright/playwright-tests.config');

  // override the config host so that it would always point to the port set by the environment.
  testConfig.serverHost = `http://localhost:${getAppConfig().port}`;
  global.testServerHost = testConfig.serverHost;

  ServerChecker.waitUntilServerIsReady(testConfig.serverHost).then(() => {
    done();
  }).catch(err => {
    logger.error('An error occurred while waiting for the e2e server.');
    logger.error(err);
    process.exit(1);
  });
});

gulp.task('flush-e2e-redisdb', function(done){
  Promise.resolve(flushRedis())
    .then(() => {
      gutil.log(`Successfully flushed redis`);
      return done();
    })
    .catch(err => {
      if (err) {
        gutil.log("Redis flush failed.");
        throw err;
      }
    });
});

gulp.task('clean-test-results', function (done) {
  const opts = {};

  var completionFunc = function() {
    done();
  };

  var errorFunc = function(err) {
    gutil.log("Error cleaning test-result files: " + err);
  };

  del([testResultPath], opts).then(completionFunc, errorFunc);
});

let dependencies = [];
if(!MODE_NO_SERVER){ // Instantiate server
  if(!MODE_NO_DIST_BUILD) {
    dependencies.push('dist');
  }

  dependencies.push('clean-test-results');
  dependencies.push('flush-e2e-redisdb');
  dependencies.push('run-e2e-server');
}

// Run dev task first before test-e2e
// Flush redis first. This should flush db no. 11 as configured in test-e2e_mock_config.json
gulp.task('test-e2e', gulp.series(dependencies, function(_done) {
  if(MODE_SERVER_ONLY){
    if (MODE_SERVER_ONLY_DAEMON) {
      logger.warn('Servers on daemon mode. We assume pm2 was run in daemon mode with prior to running this task.');
      logger.warn('To run pm2 in daemon mode simply run `pm2 list` on the command line.');
      exit(true);
    } else {
      _done();
    }
    return;
  }

  gutil.log('======================Starting e2e test======================');

  const doTest = () => {
    return new Promise((resolve, reject) => {
      const params = ['playwright', 'test'];

      const proc = spawn('npx', params);

      proc.stdout.on( 'data', data => {
        gutil.log( `stdout: ${data}` );
      });

      proc.stderr.on( 'data', data => {
        gutil.log( `stderr: ${data}` );
        reject(data);
      });

      proc.on( 'close', code => {
        gutil.log(`child process exited with code ${code}`);

        getDirectorySize(testResultPath, function(err, size) {
          if (size) {
            reject('Test results contains errors. Please see '
              + testResultPath);
          } else {
            resolve();
          }
        });
      });
    });
  };

  function done(success){
    gutil.log('gulp test-e2e passed all tests: ' + success);
    if(MODE_NO_SERVER){
      exit(success);
      return;
    }
    pm2.stop(getProcessConfig().name, function(err) {
      pm2.disconnect();   // Disconnects from PM2
      exit(success);
    });
  }

  function exit(success){
    _done();
    if(!success){
      displayInfoMessageOnError();
      process.exit(1);
    }else{
      process.exit(0);
    }
  }

  doTest().then(() => {
    done(true);
  }).catch((err) => {
    done(false);
  });

  function displayInfoMessageOnError(){
    gutil.log(` E2E Test(s) failed. `);
  }
}));

function getDirectorySize(item, callback) {
  let totalSize = 0;

  fs.lstat(item, (err, stats) => {
    if (err) return callback(err);
    if (stats.isDirectory()) {
      fs.readdir(item, (err, list) => {
        if (err) return callback(err);
        Async.each(
          list, (listItem, cb) => {
            getDirectorySize(path.join(item, listItem),
              (err, size) => {
                totalSize += size;
                cb();
              });
          }, err => {
            if (err) return callback(err);
            callback(null, totalSize);
          }
        );
      });
    } else {
      process.nextTick(function () {
        callback(null, (totalSize += stats.size));
      });
    }
  });
}
