'use strict';

const path = require('path');
const fse = require('fs-extra');
const fsp = require('fs').promises;
const chalk = require('chalk');
const weakMemoizee = require('memoizee/weak');
const _ = require('lodash');
const resolveTmpdir = require('process-utils/tmpdir');
const ServerlessError = require('../serverless-error');
const safeMoveFile = require('./fs/safeMoveFile');
const { style, legacy, log } = require('@serverless/utils/log');
const healthStatusFilename = require('./health-status-filename');

const disabledDeprecationCodesByEnv = extractCodes(process.env.SLS_DEPRECATION_DISABLE);

const notificationModeByEnv = process.env.SLS_DEPRECATION_NOTIFICATION_MODE;

const triggeredDeprecations = new Set();
const bufferedDeprecations = [];

const deprecationLogger = log.get('deprecation');

function extractCodes(codesStr) {
  if (!codesStr) {
    return new Set();
  }
  return new Set(codesStr.split(','));
}

const resolveDisabledDeprecationsByService = weakMemoizee((serviceConfig) => {
  let disabledDeprecations = [];
  if (typeof serviceConfig.disabledDeprecations === 'string') {
    disabledDeprecations = [serviceConfig.disabledDeprecations];
  } else {
    disabledDeprecations = Array.from(serviceConfig.disabledDeprecations || []);
  }
  return new Set(disabledDeprecations);
});

const resolveMode = (serviceConfig) => {
  switch (notificationModeByEnv) {
    case 'error':
    case 'warn':
      return notificationModeByEnv;
    default:
  }
  const modeByConfig = _.get(serviceConfig, 'deprecationNotificationMode');
  switch (modeByConfig) {
    case 'error':
    case 'warn':
      return modeByConfig;
    default:
      return module.exports.defaultMode;
  }
};

function writeDeprecation(code, message) {
  const prefix = 'Serverless: ';
  const messageLines = message.split('\n');
  const followingLinesPrefix = ' '.repeat(prefix.length);
  for (let i = 1; i < messageLines.length; ++i) {
    messageLines[i] = followingLinesPrefix + messageLines[i];
  }
  message = messageLines.join('\n');
  legacy.write(`Serverless: ${chalk.keyword('orange')(`Deprecation warning: ${message}`)}\n`);
  if (!code.startsWith('EXT_')) {
    legacy.write(
      `            ${chalk.keyword('orange')(
        `More Info: https://www.serverless.com/framework/docs/deprecations/#${code}`
      )}\n`
    );
  }
}

module.exports = (code, message, { serviceConfig } = {}) => {
  try {
    if (
      triggeredDeprecations.has(code) ||
      disabledDeprecationCodesByEnv.has(code) ||
      disabledDeprecationCodesByEnv.has('*')
    ) {
      return;
    }

    if (serviceConfig) {
      const serviceDisabledCodes = resolveDisabledDeprecationsByService(serviceConfig);
      if (serviceDisabledCodes.has(code) || serviceDisabledCodes.has('*')) {
        return;
      }
    }

    switch (resolveMode(serviceConfig)) {
      case 'error':
        throw new ServerlessError(
          `${message}\n  More Info: https://www.serverless.com/framework/docs/deprecations/#${code}`,
          `REJECTED_DEPRECATION_${code}`
        );
      case 'warn':
        writeDeprecation(code, message);
        return;
      default:
        bufferedDeprecations.push({ message, code });
    }
  } finally {
    triggeredDeprecations.add(code);
  }
};

module.exports.triggeredDeprecations = triggeredDeprecations;
module.exports.defaultMode = 'warn:summary';

module.exports.flushBuffered = () => {
  try {
    for (const { code, message } of bufferedDeprecations) writeDeprecation(code, message);
  } finally {
    bufferedDeprecations.length = 0;
  }
};

module.exports.printSummary = async () => {
  if (!bufferedDeprecations.length) {
    try {
      await fsp.unlink(healthStatusFilename);
    } catch {
      // ignore
    }
    return;
  }

  try {
    const healthStatus = [];

    deprecationLogger.warning();

    if (bufferedDeprecations.length === 1) {
      healthStatus.push('1 deprecation triggered in the last command:', '');
      deprecationLogger.warning(
        style.aside("1 deprecation found: run 'serverless doctor' for more details")
      );
    } else {
      healthStatus.push(
        `${bufferedDeprecations.length} deprecations triggered in the last command:`,
        ''
      );
      deprecationLogger.warning(
        style.aside(
          `${bufferedDeprecations.length} deprecations found: run 'serverless doctor' for more details`
        )
      );
    }
    for (const { code, message } of bufferedDeprecations) {
      healthStatus.push(message);
      if (!code.startsWith('EXT_')) {
        healthStatus.push(
          style.aside(`More info: https://serverless.com/framework/docs/deprecations/#${code}`)
        );
      }
    }

    const tmpHealthStatusFilename = path.resolve(await resolveTmpdir(), 'health-status');
    await Promise.all([
      fse.ensureDir(path.dirname(healthStatusFilename)),
      fsp.writeFile(tmpHealthStatusFilename, healthStatus.join('\n')),
    ]);
    await safeMoveFile(tmpHealthStatusFilename, healthStatusFilename);

    if (bufferedDeprecations.length === 1) {
      const { code, message } = bufferedDeprecations[0];
      writeDeprecation(code, message);
      return;
    }
    const prefix = 'Serverless: ';
    legacy.write(`${prefix}${chalk.bold.keyword('orange')('Deprecation warnings:')}\n\n`);
    for (const { code, message } of bufferedDeprecations) {
      const messageLines = chalk.bold(message).split('\n');
      if (!code.startsWith('EXT_')) {
        messageLines.push(
          `More Info: https://www.serverless.com/framework/docs/deprecations/#${code}`
        );
      }

      legacy.write(chalk.keyword('orange')(`${messageLines.join('\n')}\n\n`));
    }
  } finally {
    bufferedDeprecations.length = 0;
  }
};
