'use strict';

const os = require('os');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const ci = require('ci-info');
const BbPromise = require('bluebird');
const fse = BbPromise.promisifyAll(require('fs-extra'));
const _ = require('lodash');
const fileExistsSync = require('../utils/fs/fileExistsSync');
const writeFileSync = require('../utils/fs/writeFileSync');
const copyDirContentsSync = require('../utils/fs/copyDirContentsSync');
const readFileSync = require('../utils/fs/readFileSync');
const walkDirSync = require('../utils/fs/walkDirSync');
const dirExistsSync = require('../utils/fs/dirExistsSync');
const isDockerContainer = require('is-docker');
const version = require('../../package.json').version;
const tracking = require('../utils/tracking');
const configUtils = require('../utils/config');
const awsArnRegExs = require('../plugins/aws/utils/arnRegularExpressions');

class Utils {
  constructor(serverless) {
    this.serverless = serverless;
  }

  getVersion() {
    return version;
  }

  dirExistsSync(dirPath) {
    return dirExistsSync(dirPath);
  }

  getTmpDirPath() {
    const dirPath = path.join(
      os.tmpdir(),
      'tmpdirs-serverless',
      crypto.randomBytes(8).toString('hex')
    );
    fse.ensureDirSync(dirPath);
    return dirPath;
  }

  fileExistsSync(filePath) {
    return fileExistsSync(filePath);
  }

  writeFileDir(filePath) {
    return fse.mkdirsSync(path.dirname(filePath));
  }

  writeFileSync(filePath, contents, cycles) {
    return writeFileSync(filePath, contents, cycles);
  }

  writeFile(filePath, contents, cycles) {
    const that = this;
    return new BbPromise((resolve, reject) => {
      try {
        that.writeFileSync(filePath, contents, cycles);
      } catch (e) {
        reject(e);
      }
      resolve();
    });
  }

  appendFileSync(filePath, conts) {
    const contents = conts || '';

    return new BbPromise((resolve, reject) => {
      try {
        fs.appendFileSync(filePath, contents);
      } catch (e) {
        reject(e);
      }
      resolve();
    });
  }

  readFileSync(filePath) {
    return readFileSync(filePath);
  }

  readFile(filePath) {
    const that = this;
    let contents;
    return new BbPromise((resolve, reject) => {
      try {
        contents = that.readFileSync(filePath);
      } catch (e) {
        reject(e);
      }
      resolve(contents);
    });
  }

  walkDirSync(dirPath) {
    return walkDirSync(dirPath);
  }

  copyDirContentsSync(srcDir, destDir) {
    return copyDirContentsSync(srcDir, destDir);
  }

  generateShortId(length) {
    return Math.random()
      .toString(36)
      .substr(2, length);
  }

  findServicePath(customPath) {
    let servicePath = null;

    if (customPath) {
      if (fileExistsSync(path.join(process.cwd(), customPath))) {
        servicePath = process.cwd();
      } else {
        throw new Error(`Config file ${customPath} not found`);
      }
    } else if (fileExistsSync(path.join(process.cwd(), 'serverless.yml'))) {
      servicePath = process.cwd();
    } else if (fileExistsSync(path.join(process.cwd(), 'serverless.yaml'))) {
      servicePath = process.cwd();
    } else if (fileExistsSync(path.join(process.cwd(), 'serverless.json'))) {
      servicePath = process.cwd();
    } else if (fileExistsSync(path.join(process.cwd(), 'serverless.js'))) {
      servicePath = process.cwd();
    }

    return servicePath;
  }

  logStat(serverless, context) {
    // the context in which serverless was executed (e.g. "install", "usage", "uninstall", ...)
    context = context || 'usage'; //eslint-disable-line

    // Service values
    const service = serverless.service;
    const resources = service.resources;
    const provider = service.provider;
    const functions = service.functions;

    // CLI inputs
    const options = serverless.processedInput.options;
    const commands = serverless.processedInput.commands;

    return new BbPromise(resolve => {
      const config = configUtils.getConfig();
      const userId = config.frameworkId;
      const trackingDisabled = config.trackingDisabled;
      const invocationId = serverless.invocationId;

      if (trackingDisabled) {
        return resolve();
      }

      let serviceName = '';
      if (service && service.service && service.service.name) {
        serviceName = service.service.name;
      } else if (service && typeof service.service === 'string') {
        serviceName = service.service;
      }

      // filter out the whitelisted options
      const whitelistedOptionKeys = ['help', 'disable', 'enable'];
      const optionKeys = Object.keys(options);

      const filteredOptionKeys = optionKeys.filter(
        key => whitelistedOptionKeys.indexOf(key) !== -1
      );

      const filteredOptions = {};
      filteredOptionKeys.forEach(key => {
        filteredOptions[key] = options[key];
      });

      // function related information retrieval
      const numberOfFunctions = _.size(functions);

      const memorySizeAndTimeoutPerFunction = [];
      if (numberOfFunctions) {
        _.forEach(functions, func => {
          const memorySize =
            Number(func.memorySize) || Number(this.serverless.service.provider.memorySize) || 1024;
          const timeout =
            Number(func.timeout) || Number(this.serverless.service.provider.timeout) || 6;

          const memorySizeAndTimeoutObject = {
            memorySize,
            timeout,
          };

          memorySizeAndTimeoutPerFunction.push(memorySizeAndTimeoutObject);
        });
      }

      // event related information retrieval
      const numberOfEventsPerType = [];
      const eventNamesPerFunction = [];
      let hasIAMAuthorizer = false;
      let hasCustomAuthorizer = false;
      let hasCognitoAuthorizer = false;
      if (numberOfFunctions) {
        _.forEach(functions, func => {
          if (func.events) {
            const funcEventsArray = [];

            func.events.forEach(event => {
              const name = Object.keys(event)[0];
              funcEventsArray.push(name);

              const alreadyPresentEvent = _.find(numberOfEventsPerType, { name });
              if (alreadyPresentEvent) {
                alreadyPresentEvent.count++;
              } else {
                numberOfEventsPerType.push({
                  name,
                  count: 1,
                });
              }

              // For HTTP events, see what authorizer types are enabled
              if (_.has(event, 'http.authorizer')) {
                if (
                  (_.isString(event.http.authorizer) &&
                    _.toUpper(event.http.authorizer) === 'AWS_IAM') ||
                  (event.http.authorizer.type &&
                    _.toUpper(event.http.authorizer.type) === 'AWS_IAM')
                ) {
                  hasIAMAuthorizer = true;
                }
                // There are three ways a user can specify a Custom authorizer:
                // 1) By listing the name of a function in the same service OR a function ARN for
                //    the authorizer property.
                // 2) By listing the name of a function in the same service for the name property
                //    in the authorizer object.
                // 3) By listing a function's ARN in the arn property of the authorizer object.

                if (
                  (_.isString(event.http.authorizer) &&
                    _.toUpper(event.http.authorizer) !== 'AWS_IAM' &&
                    !awsArnRegExs.cognitoIdpArnExpr.test(event.http.authorizer)) ||
                  event.http.authorizer.name ||
                  (event.http.authorizer.arn &&
                    awsArnRegExs.lambdaArnExpr.test(event.http.authorizer.arn))
                ) {
                  hasCustomAuthorizer = true;
                }
                if (
                  (_.isString(event.http.authorizer) &&
                    awsArnRegExs.cognitoIdpArnExpr.test(event.http.authorizer)) ||
                  (event.http.authorizer.arn &&
                    awsArnRegExs.cognitoIdpArnExpr.test(event.http.authorizer.arn))
                ) {
                  hasCognitoAuthorizer = true;
                }
              }
            });

            eventNamesPerFunction.push(funcEventsArray);
          }
        });
      }

      let hasCustomResourcesDefined = false;
      // check if configuration in resources.Resources is defined
      if (resources && resources.Resources && Object.keys(resources.Resources).length) {
        hasCustomResourcesDefined = true;
      }
      // check if configuration in resources.Outputs is defined
      if (resources && resources.Outputs && Object.keys(resources.Outputs).length) {
        hasCustomResourcesDefined = true;
      }

      let hasCustomVariableSyntaxDefined = false;
      const defaultVariableSyntax = '\\${([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*?]+?)}';

      // check if the variableSyntax in the provider section is defined
      if (
        provider &&
        provider.variableSyntax &&
        provider.variableSyntax !== defaultVariableSyntax
      ) {
        hasCustomVariableSyntaxDefined = true;
      }

      const data = {
        userId,
        event: 'framework_stat',
        properties: {
          version: 2,
          command: {
            name: commands.join(' '),
            filteredOptions,
            isRunInService: !!serverless.config.servicePath,
          },
          service: {
            numberOfCustomPlugins: _.size(service.plugins),
            hasCustomResourcesDefined,
            hasVariablesInCustomSectionDefined: !!service.custom,
            hasCustomVariableSyntaxDefined,
            name: serviceName,
          },
          provider: {
            name: provider.name,
            runtime: provider.runtime,
            stage: provider.stage,
            region: provider.region,
          },
          functions: {
            numberOfFunctions,
            memorySizeAndTimeoutPerFunction,
          },
          events: {
            numberOfEvents: numberOfEventsPerType.length,
            numberOfEventsPerType,
            eventNamesPerFunction,
          },
          general: {
            userId,
            context,
            invocationId,
            timestamp: new Date().getTime(),
            timezone: new Date().toString().match(/([A-Z]+[+-][0-9]+)/)[1],
            operatingSystem: process.platform,
            userAgent: process.env.SERVERLESS_DASHBOARD ? 'dashboard' : 'cli',
            serverlessVersion: serverless.version,
            nodeJsVersion: process.version,
            isDockerContainer: isDockerContainer(),
            isCISystem: ci.isCI,
            ciSystem: ci.name,
          },
        },
      };

      if (config.userId && data.properties && data.properties.general) {
        // add platformId to segment call
        data.properties.general.platformId = config.userId;
      }

      if (provider && provider.name && provider.name.toUpperCase() === 'AWS' && data.properties) {
        data.properties.aws = {
          hasIAMAuthorizer,
          hasCustomAuthorizer,
          hasCognitoAuthorizer,
        };
      }

      return resolve(data);
    }).then(data => {
      if (data) tracking.track('segment', data);
      // prevent Bluebird `Warning: a promise was created in a handler but was not returned from it` by returning null
      return null;
    });
  }

  getLocalAccessKey() {
    const userConfig = configUtils.getConfig();
    const currentId = userConfig.userId;
    const globalConfig = configUtils.getGlobalConfig();
    const username = _.get(globalConfig, `users[${currentId}].dashboard.username`);
    return (
      _.get(globalConfig, `users[${currentId}].dashboard.accessKey`, false) ||
      _.get(globalConfig, `users[${currentId}].dashboard.accessKeys[${username}]`, false)
    );
  }

  isEventUsed(functions, eventName) {
    return _.reduce(
      functions,
      (accum, func) => {
        const events = func.events || [];
        if (events.length) {
          events.forEach(event => {
            if (Object.keys(event)[0] === eventName) {
              accum = true; // eslint-disable-line no-param-reassign
            }
          });
        }
        return accum;
      },
      false
    );
  }
}

module.exports = Utils;
