const path = require('path'),

      VERSION_FILE = 'version.json',
      STATUS = {
        INSTALLED: 'installed',
        FAILED: 'failed',
        SKIPPED: 'skipped'
      },
      appTempPath = process.env.APP_TEMP_PATH, // eslint-disable-line no-process-env

      extractZip = require('./extract-zip'),
      FileService = require('./FileService'),
      PluginResolver = require('./PluginResolver'),
      PrepackagedPluginResolver = require('./PrepackagedPluginResolver');

/**
 * This will make sure that all the plugins packaged with the App
 * are installed with the compatible version.
 * The plugins which are not installed, will be installed
 * The plugins which are outdated, will be updated
 * The plugins which are not compatible, will be downgraded (to the version that was shipped with the App)
 *
 * @returns {Promise}
 */
async function installOrUpdatePlugins () {
  let builtInPlugins = await PrepackagedPluginResolver.getPrepackedInPlugins(),
      pluginInstallPromises = Object.keys(builtInPlugins).map((pluginId) => {
        let version = builtInPlugins[pluginId];

        return Promise.resolve()

          // Check if the plugin is installed
          .then(() => {
            return _isInstalled(pluginId, version);
          })

          // If the plugin is not installed or is outdated then install it
          .then(async (isInstalled) => {
            if (!isInstalled || await _shouldReInstallPlugin(pluginId, version)) {
              pm.logger.info('PluginInstallationService~installOrUpdatePlugins Installing plugin',
                { version, pluginId, isInstalled });
              return installFromLocal(pluginId, version);
            }
            return { pluginId, version, status: STATUS.SKIPPED };

          });
      });

  return Promise.all(pluginInstallPromises);
}

/**
 * Installs a built-in plugin
 *
 * @param {String} pluginId
 * @param {String} version
 * @return {Promise}
 */
function installFromLocal (pluginId, version) {
  let logPrefix = 'PluginInstallationService~installFromLocal',
      pluginZipPath = PrepackagedPluginResolver.getPluginPath(pluginId),
      pluginsDir = PluginResolver.getPluginsDirectory(),
      currVersionDirPath = path.resolve(pluginsDir, pluginId, version),
      tempDir = appTempPath,
      result = { pluginId, version, status: null }; // if status is 'failed' will have an 'error' property

  pm.logger.info(logPrefix, { pluginZipPath, pluginsDir, currVersionDirPath, tempDir, result });

  return Promise.resolve()

    // extract the plugin zip to a temp location (so that we can rename the directory)
    .then(() => {
      return new Promise((resolve) => {
        pm.logger.info(`${logPrefix} extracting`);

        extractZip(pluginZipPath, { dir: tempDir }, (err) => {
          if (err) {
            result.status = STATUS.FAILED;
            result.error = new Error(`${logPrefix}: Error while extracting plugin: ${err.message}`);
            pm.logger.warn(`${logPrefix}: Error while extracting plugin`, err);
          }
          else {
            pm.logger.info(`${logPrefix} extracted`, result);
          }

          resolve();
        });
      });
    })

    // Move the extracted plugin from temp location to plugin installation directory
    .then(() => {
      if (result.status === STATUS.FAILED) {
        return;
      }

      let extractedPluginPath = path.resolve(tempDir, pluginId);

      pm.logger.info(`${logPrefix} moving`, { extractedPluginPath });
      return FileService.move(extractedPluginPath, currVersionDirPath);
    })

    // Validate the plugin
    .then(() => {
      if (result.status === STATUS.FAILED) {
        return;
      }

      // @TODO
      return true;
    })

    // If plugin was validated then update the version.json file
    // otherwise do the cleanup
    .then((validated) => {
      if (result.status === STATUS.FAILED) {
        return;
      }

      if (!validated) {
        result.status = STATUS.FAILED;
        result.error = new Error('PluginInstallationService~installFromLocal: Plugin validation failed');
        return FileService.remove(currVersionDirPath);
      }

      let versionFilePath = path.resolve(pluginsDir, pluginId, VERSION_FILE),
          versionContents = JSON.stringify({ latest: version });

      pm.logger.info('PluginInstallationService~installFromLocal updating version.json', versionContents);
      return FileService.writeFile(versionFilePath, versionContents);
    })

    // Update the status
    .then(() => {
      if (result.status === STATUS.FAILED) {
        return;
      }

      result.status = STATUS.INSTALLED;
    })

    // Catch any error and update the result
    .catch((err) => {
      result.status = STATUS.FAILED;
      result.error = new Error(`PluginInstallationService~installFromLocal: Error while installing ${err.message}`);
      pm.logger.warn('PluginInstallationService~installFromLocal Caught an error', err, result);
    })

    // resolve with the result object
    .then(() => {
      pm.logger.info('PluginInstallationService~installFromLocal Completed installing plugin', result);
      return result;
    });
}

/**
 * Checks whether a plugin with a particular version is installed or not
 *
 * @param {String} pluginId
 * @param {String} version
 * @return {Promise<Boolean>}
 */
async function _isInstalled (pluginId, version) {
  let pluginCurrentVersion = await PluginResolver.getPluginCurrentVersion(pluginId);

  return pluginCurrentVersion === version;
}

/**
 * Checks whether the plugin should be re-installed or not
 * For now it just checks if the installed version and the version which was packaged with App
 * and same or not when we introduce network-updates we will have to check the compatibility too
 *
 * @param {String} pluginId
 * @param {String} version
 * @return {Promise<Boolean>}
 */
function _shouldReInstallPlugin (pluginId, version) {
  return PluginResolver.getPackageJSON(pluginId)
    .then((packageJson) => {
      pm.logger.info('PluginInstallationService~_shouldReInstallPlugin',
        { installedVersion: packageJson && packageJson.version, currentVersion: version });
      return version !== (packageJson && packageJson.version);
    });
}

module.exports = {
  installOrUpdatePlugins,
  installFromLocal
};
