// TODO: Remove in v3

'use strict';

const BbPromise = require('bluebird');
const childProcess = BbPromise.promisifyAll(require('child_process'));
const fsp = require('fs').promises;
const fse = require('fs-extra');
const path = require('path');
const _ = require('lodash');
const isPlainObject = require('type/plain-object/is');
const yaml = require('js-yaml');
const cloudformationSchema = require('@serverless/utils/cloudformation-schema');
const { legacy, log, progress, style } = require('@serverless/utils/log');
const cliCommandsSchema = require('../../cli/commands-schema');
const yamlAstParser = require('../../utils/yamlAstParser');
const pluginUtils = require('./lib/utils');

const mainProgress = progress.get('main');

const requestManualUpdate = (serverlessFilePath) => {
  legacy.log(`
  Can't automatically remove plugin from "${path.basename(serverlessFilePath)}" file.
  Please make it manually.
`);
  log.notice();
  log.notice.skip(
    `Can't automatically remove plugin from "${path.basename(
      serverlessFilePath
    )}" file. Please make it manually.`
  );
};

class PluginUninstall {
  constructor(serverless, options) {
    this.serverless = serverless;
    this.options = options;

    Object.assign(this, pluginUtils);

    this.commands = {
      plugin: {
        commands: {
          uninstall: {
            ...cliCommandsSchema.get('plugin uninstall'),
          },
        },
      },
    };

    this.hooks = {
      'plugin:uninstall:uninstall': async () => BbPromise.bind(this).then(this.uninstall),
    };
  }

  async uninstall() {
    const pluginInfo = pluginUtils.getPluginInfo(this.options.name);
    this.options.pluginName = pluginInfo[0];

    return BbPromise.bind(this)
      .then(this.validate)
      .then(this.getPlugins)
      .then((plugins) => {
        const plugin = plugins.find((item) => item.name === this.options.pluginName);
        if (!plugin) {
          legacy.log('Plugin not found in serverless registry, continuing to uninstall');
        }
        return BbPromise.bind(this)
          .then(this.uninstallPeerDependencies)
          .then(this.pluginUninstall)
          .then(this.removePluginFromServerlessFile)
          .then(() => {
            legacy.log(`Successfully uninstalled "${this.options.pluginName}"`);
            log.notice();
            log.notice.success(
              `Plugin "${this.options.pluginName}" uninstalled ${style.aside(
                `(${Math.floor(
                  (Date.now() - this.serverless.pluginManager.commandRunStartTime) / 1000
                )}s)`
              )}`
            );
            return BbPromise.resolve();
          });
      });
  }

  async pluginUninstall() {
    legacy.log(
      `Uninstalling plugin "${this.options.pluginName}" (this might take a few seconds...)`
    );
    mainProgress.notice(`Uninstalling plugin "${this.options.pluginName}"`, { isMainEvent: true });
    return this.npmUninstall(this.options.pluginName);
  }

  async removePluginFromServerlessFile() {
    const serverlessFilePath = this.getServerlessFilePath();
    const fileExtension = path.extname(serverlessFilePath);
    if (fileExtension === '.js' || fileExtension === '.ts') {
      requestManualUpdate(serverlessFilePath);
      return;
    }

    if (_.last(serverlessFilePath.split('.')) === 'json') {
      const serverlessFileObj = await fse.readJson(serverlessFilePath);
      const isArrayPluginsObject = Array.isArray(serverlessFileObj.plugins);
      const plugins = isArrayPluginsObject
        ? serverlessFileObj.plugins
        : serverlessFileObj.plugins && serverlessFileObj.plugins.modules;

      if (plugins) {
        _.pull(plugins, this.options.pluginName);
        if (!plugins.length) {
          if (isArrayPluginsObject) {
            delete serverlessFileObj.plugins;
          } else {
            delete serverlessFileObj.plugins.modules;
          }
        }
        await fse.writeJson(serverlessFilePath, serverlessFileObj);
      }
      return;
    }

    const serverlessFileObj = yaml.load(await fsp.readFile(serverlessFilePath, 'utf8'), {
      filename: serverlessFilePath,
      schema: cloudformationSchema,
    });
    if (serverlessFileObj.plugins != null) {
      // Plugins section can be behind veriables, opt-out in such case
      if (isPlainObject(serverlessFileObj.plugins)) {
        if (
          serverlessFileObj.plugins.modules != null &&
          !Array.isArray(serverlessFileObj.plugins.modules)
        ) {
          requestManualUpdate(serverlessFilePath);
          return;
        }
      } else if (!Array.isArray(serverlessFileObj.plugins)) {
        requestManualUpdate(serverlessFilePath);
        return;
      }
    }
    await yamlAstParser.removeExistingArrayItem(
      serverlessFilePath,
      Array.isArray(serverlessFileObj.plugins) ? 'plugins' : 'plugins.modules',
      this.options.pluginName
    );
  }

  async uninstallPeerDependencies() {
    const pluginPackageJsonFilePath = path.join(
      this.serverless.serviceDir,
      'node_modules',
      this.options.pluginName,
      'package.json'
    );
    return fse
      .readJson(pluginPackageJsonFilePath)
      .then((pluginPackageJson) => {
        if (pluginPackageJson.peerDependencies) {
          const pluginsArray = [];
          Object.keys(pluginPackageJson.peerDependencies).forEach((k) => {
            pluginsArray.push(k);
          });
          return BbPromise.map(pluginsArray, this.npmUninstall);
        }
        return BbPromise.resolve();
      })
      .catch(() => BbPromise.resolve());
  }

  async npmUninstall(name) {
    return childProcess.execAsync(`npm uninstall --save-dev ${name}`, {
      stdio: 'ignore',
    });
  }
}

module.exports = PluginUninstall;
