#! /usr/bin/env node

'use strict';

const commandLineArgs = require('command-line-args');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const s3 = require('s3');

const rootPath = __dirname.split('node_modules')[0];
const packagePath = rootPath + '/package.json';
const pkg = require(packagePath);
const inquirer = require('inquirer');
require('util.promisify/shim')();
const {promisify} = require('util');
const _ = require('lodash');

let bucket;
let appName;
let target;

// Parse inline arguments
const optionDefinitions = [
  { name: 'bucket', alias: 'b', type: String },
  { name: 'appName', alias: 'a', type: String },
  { name: 'target', alias: 't', type: String },
];
const options = commandLineArgs(optionDefinitions);

const appsDestinationPrefix = 'i18n/apps'

/**
 * Entry point for the script.
 */
promptForData().then(function(answer) {
  if (answer.s3Bucket) {
    bucket = answer.s3Bucket;
  }
  if (answer.target) {
    target = answer.target;
  }
  if (target === 'gateway') {
    return releaseProject('gateway', 'i18n/gateway');
  } else if (target === 'dashboard project') {
    return releaseProject('dashboard', 'i18n/dashboard');
  } else if (target === 'everything') {
    return releaseEverything();
  } else if (target === 'app') {
    return releaseApp();
  }
})

/**
 * Asks user to which bucket the App should be released.
 */
function promptForData() {
  const questions = [];
  if (!options['bucket']) {
    questions.push({
      message: 'Which bucket do you want to use?',
      name: 's3Bucket',
      type: 'list',
      choices: pkg.config.aws.buckets
    });
  } else {
    bucket = options.bucket;
  }

  if (!options['target']) {
    questions.push({
      message: 'What project\'s localization files you want to release?',
      name: 'target',
      type: 'list',
      choices: [
        'app',
        'gateway',
        'dashboard project',
        'everything'
      ]
    });
  } else {
    target = options.target;
  }
  return inquirer.prompt(questions);
}

/** Release app files */

function releaseApp() {
  return askForAppName().then((response) => {
    if (response.confirm) {
      return syncAppTranslations(bucket, appName);
    }
  }).then(result => {
    console.log('Release succesful, invalidating cache...');
    return invalidateAppCache(appName);
  });
}

function askForAppName() {
  let appCollection;
  return collectAppNames().then((apps) => {
    appCollection = apps;
    if (!options['appName']) {
      return promptForApp(appCollection).then(answer => { 
        appName = answer.appName; 
        return confirmDestination(appName);
      });
    } else {
      appName = options.appName;
      return confirmDestination(appName);
    }
  });
}

/** Releases every translation */
function releaseEverything() {
  return confirmReleaseEverything().then((answer) => {
    if (answer.confirm) {
      return syncEverything();
    } else {
      return Promise.reject('canceled');
    }
  }).then(() => {
    console.log('Release finished, invalidating cache...');
    return invalidatePaths(['/i18n/*']);
  }).then(() => {
    console.log('Everything released!');
  }, error => {
    console.log('Release not finished: ', error);
  });
}


function confirmReleaseEverything() {
  return inquirer.prompt({
    message: `All localization files will be overwritten with what is currently in this directory. Are you sure?`,
    name: 'confirm',
    type: 'confirm'
  });
}

function syncEverything() {
  let s3Client = createS3Client();
  return Promise.all([
    syncDirectory('apps', s3Client, bucket, 'i18n/apps'),
    syncDirectory('dashboard', s3Client, bucket, 'i18n/dashboard'),
    syncDirectory('gateway', s3Client, bucket, 'i18n/gateway')
  ]).catch(err => {
    console.log('Error releasing everything: ', err.stack);
  });
}

/** Releases every apps translation */
function releaseAllApps() {
  return confirmReleaseAllApps().then((answer) => {
    if (answer.confirm) {
      return syncAllApps();
    } else {
      return Promise.reject('canceled');
    }
  }).then(() => {
    console.log('Release finished, invalidating cache...');
    return invalidatePaths(['/i18n/apps/*']);
  }).then(() => {
    console.log('Every app localization file has been released!');
  }, error => {
    console.log('Release not finished: ', error);
  });
}



function confirmReleaseAllApps() {
  return inquirer.prompt({
    message: `Every app localization file will be overwritten with what is currently in this directory. Are you sure?`,
    name: 'confirm',
    type: 'confirm'
  });
}

function syncAllApps() {
  let s3Client = createS3Client();
  return Promise.all([
    syncDirectory('apps', s3Client, bucket, 'i18n/apps'),
    syncDirectory('dashboard', s3Client, bucket, 'i18n/dashboard'),
    syncDirectory('gateway', s3Client, bucket, 'i18n/gateway')
  ]).catch(err => {
    console.log('Error releasing everything: ', err.stack);
  });
}

/* Release gateway */
function releaseProject(name, releaseDir) {
  return confirmReleaseProject(name, releaseDir).then((answer) => {
    if (answer.confirm) {
      return syncProject(name, releaseDir);
    } else {
      return Promise.reject('canceled');
    }
  }).then(() => {
    console.log('Release finished, invalidating cache...');
    return invalidatePaths([`/${releaseDir}/*`]);
  }).then(() => {
    console.log(`${name} localization files released!`);
  }, error => {
    console.log('Release not finished: ', error);
  })
}

function confirmReleaseProject(name, releaseDir) {
  return inquirer.prompt({
    message: `Confirm release of project ${name} localization files to ${bucket}/${releaseDir}`,
    name: 'confirm',
    type: 'confirm'
  });
}

function syncProject(localDir, remoteDir) {
  let s3Client = createS3Client();
  return syncDirectory(localDir, s3Client, bucket, remoteDir);
}


/** Common functions */
/**
 * Looks into `dashboard` and `player` directories and gathers list of apps' files in each of them.
 */
function collectAppNames() {
  const readdirAsync = promisify(fs.readdir);
  return readdirAsync('apps/');
}


/**
 * Asks user which app's localization strings should be released.
 */
function promptForApp(appList) {
  if (!options['appName']) {
    return inquirer.prompt({ 
      message: `Which app's files do you want to release?`,
      name: 'appName',
      type: 'list',
      choices: appList
    });
  } else Promise.resolve(options['appName']);
}


function confirmDestination(appName) {
  let uploadPath = path.posix.join(bucket, appsDestinationPrefix, appName);
  return inquirer.prompt({
    message: `Confirm release of localization files for '${appName}' to ${uploadPath}`,
    name: 'confirm',
    type: 'confirm'
  });
}

/**
 *  Creates an S3 client.
 */
function createS3Client() {
  let creds;
  const s3Options = Object.assign({}, pkg.config.aws.s3);

  try {
    creds = JSON.parse(fs.readFileSync(path.resolve(rootPath, './aws.private.json'), 'utf8'));
  } catch (e) {
    console.error(`Error finding/parsing ${chalk.default.cyan('aws.private.json')}`);
    throw e;
  }

  if (creds.accessKeyId == null || creds.secretAccessKey == null) {
    throw new TypeError(`Error could not find accessKeyId or secretAccessKey in ${chalk.default.cyan('aws.private.json')} file.`);
  }

  Object.assign(s3Options, creds);

  // s3Options is what is used by the AWS SDK
  // See: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#constructor-property

  return s3.createClient({
    // default options
    maxAsyncS3: 20,
    s3RetryCount: 3,
    s3RetryDelay: 1000,
    multipartUploadThreshold: 20971520,
    multipartUploadSize: 15728640,
    cacheTTL: 0,
    s3Options
  });
}


/**
 *
 * @param {*} uploader
 */
function promiseWrapUploader(uploader) {
  return new Promise(function (resolve, reject) {
    uploader.on('error', reject);
    uploader.on('end', resolve);
    uploader.on('fileUploadEnd', function (localFilePath, s3Key) {
      console.log(`Pushed ${chalk.default.yellow(localFilePath)} to ${chalk.default.yellow(s3Key)}`);
    });
  });
}


/**
 *
 * @param {*} bucket
 * @param {*} appName
 */
function syncAppTranslations(bucket, appName) {
  if (appName.startsWith('/')) {
    appName = appName.substr(1);
  }

  let s3Client = createS3Client();

  return syncDirectory(`apps/${appName}`, s3Client, bucket, `i18n/apps/${appName}`).catch(function (error) {
    console.error('There was an error during the release process!');
    console.error(error.stack);
    return null;
  });
}

function syncDirectory(localDir, s3Client, bucket, remoteDir) {
  let uploadOptions = {
    localDir: localDir,
    deleteRemoved: true,
    s3Params: {
      Bucket: bucket,
      Prefix: remoteDir,
      // can add options for putObject command
      // see: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property
    }
  };
  return promiseWrapUploader(s3Client.uploadDir(uploadOptions));
}

/**
 * Invalidates cache for `appName`.
 * @param {string} appName
 * @returns {Promise}
 */
function invalidateAppCache(appName) {
  const paths = [
    `/i18n/apps/${appName}/dashboard/*`,
    `/i18n/apps/${appName}/player/*`
  ];
  return invalidatePaths(paths);
}


/**
 * Invalidates cache on paths that are passed.
 * @param {Array<string>} paths - paths to invalidate.
 * @returns {Promise}
 */
function invalidatePaths(paths) {
  return new Promise((resolve, reject) => {
    const distributionIds = {
      'dev-apps.enplug.in': 'E3IQ50D070L7PA',
      'apps.enplug.in': 'EG84KEU04G44V',
      'apps.enplug.com': 'E1YGTSTR7FTNEX'
    }
    s3.AWS.config.loadFromPath('./aws.private.json');
    const cloudfront = new s3.AWS.CloudFront();
    var params = {
      DistributionId: distributionIds[bucket],
      InvalidationBatch: {
        CallerReference: String(Date.now()),
        Paths: {
          Quantity: paths.length,
          Items: paths
        }
      }
    };
    cloudfront.createInvalidation(params, function(err, data) {
      if (err) {
        console.error(`Cache invalidation error: ${err}`);
        reject(err);
      } else {
        console.log('Cache invalidation finished.');
        resolve(data);
      }
    });
  });
}