'use strict';

const p = require('path');
const os = require('os');
const fs = require('fs');

const _ = require('lodash');
const writeFileAtomic = require('write-file-atomic');
const uuid = require('uuid');
const log = require('./log');

let baseFilename = 'serverless';
if (process.env.SERVERLESS_PLATFORM_STAGE && process.env.SERVERLESS_PLATFORM_STAGE !== 'prod') {
  baseFilename = `serverless${process.env.SERVERLESS_PLATFORM_STAGE.toLowerCase()}`;
  baseFilename = baseFilename.trim();
}
baseFilename = `.${baseFilename}rc`;

const getLocalConfigPath = () => p.join(process.cwd(), baseFilename);
const getDefaultGlobalConfigPath = () => p.join(os.homedir(), baseFilename);
const getHomeConfigGlobalConfigPath = () => p.join(os.homedir(), '.config', baseFilename);

const getGlobalConfigPath = () => {
  const homeConfigGlobalConfigPath = getHomeConfigGlobalConfigPath();
  const defaultGlobalConfigPath = getDefaultGlobalConfigPath();
  const homeConfigGlobalConfigExists = fs.existsSync(homeConfigGlobalConfigPath);
  const defaultGlobalConfigExists = fs.existsSync(defaultGlobalConfigPath);

  if (homeConfigGlobalConfigExists && defaultGlobalConfigExists) {
    log(`Found two global configuration files. Using: ${defaultGlobalConfigPath}`, {
      color: 'orange',
    });
    return defaultGlobalConfigPath;
  }

  if (homeConfigGlobalConfigExists) {
    return homeConfigGlobalConfigPath;
  }

  return defaultGlobalConfigPath;
};

function storeConfig(config, configPath) {
  config.meta = config.meta || {};
  config.meta.updated_at = Math.round(Date.now() / 1000);

  const jsonConfig = JSON.stringify(config, null, 2);

  try {
    writeFileAtomic.sync(configPath, jsonConfig);
  } catch (error) {
    if (process.env.SLS_DEBUG) {
      log(error.stack, { color: 'red' });
      log(`Unable to store serverless config: ${configPath} due to ${error.code} error`, {
        color: 'red',
      });
    }
  }
}

function createDefaultGlobalConfig() {
  const defaultConfig = {
    userId: null, // currentUserId
    frameworkId: uuid.v1(),
    trackingDisabled: false,
    enterpriseDisabled: false,
    meta: {
      created_at: Math.round(Date.now() / 1000), // config file creation date
      updated_at: null, // config file updated date
    },
  };
  storeConfig(defaultConfig, getDefaultGlobalConfigPath());
  return defaultConfig;
}

function getLocalConfig() {
  const localConfigPath = getLocalConfigPath();
  try {
    return JSON.parse(fs.readFileSync(localConfigPath));
  } catch (error) {
    if (error.code === 'ENOENT') return {};
    log(`User Configuration warning: Cannot resolve local config file.\nError: ${error.message}`, {
      color: 'orange',
    });
    try {
      // try/catch to account for very unlikely race condition where file existed
      // during readFileSync but no longer exists during rename
      const backupServerlessrcPath = `${localConfigPath}.bak`;
      fs.renameSync(localConfigPath, backupServerlessrcPath);
      log(`Your previous local config was renamed to ${backupServerlessrcPath} for debugging.`, {
        color: 'orange',
      });
    } catch {
      // Ignore
    }
  }

  return {};
}

function getGlobalConfig() {
  const globalConfigPath = getGlobalConfigPath();
  try {
    return JSON.parse(fs.readFileSync(globalConfigPath));
  } catch (error) {
    // If the file does not exist, we want to recreate default global configuration
    if (error.code !== 'ENOENT') {
      log(
        `User Configuration warning: Cannot resolve global config file: ${globalConfigPath} \nError: ${error.message}`,
        {
          color: 'orange',
        }
      );
      try {
        // try/catch to account for very unlikely race condition where file existed
        // during readFileSync but no longer exists during rename
        const backupServerlessrcPath = `${globalConfigPath}.bak`;
        fs.renameSync(globalConfigPath, backupServerlessrcPath);
        log(
          `Your previous global config was renamed to ${backupServerlessrcPath} for debugging. Default global config will be recreated under ${getDefaultGlobalConfigPath()}.`,
          { color: 'orange' }
        );
      } catch {
        // Ignore
      }
    }
  }

  return createDefaultGlobalConfig();
}

function getConfig() {
  const localConfig = getLocalConfig();
  const globalConfig = getGlobalConfig();
  return _.merge(globalConfig, localConfig);
}

function getConfigForUpdate() {
  const localConfigPath = getLocalConfigPath();
  const localConfigExists = fs.existsSync(localConfigPath);
  if (localConfigExists) {
    return {
      config: getLocalConfig(),
      configPath: localConfigPath,
    };
  }

  return {
    config: getGlobalConfig(),
    configPath: getGlobalConfigPath(),
  };
}

function set(key, value) {
  const configForUpdate = getConfigForUpdate();
  let { config } = configForUpdate;
  const { configPath } = configForUpdate;
  if (key && typeof key === 'string' && typeof value !== 'undefined') {
    config = _.set(config, key, value);
  } else if (_.isObject(key)) {
    config = _.merge(config, key);
  } else if (typeof value !== 'undefined') {
    config = _.merge(config, value);
  }
  storeConfig(config, configPath);
  return getConfig();
}

function deleteValue(key) {
  const configForUpdate = getConfigForUpdate();
  let { config } = configForUpdate;
  const { configPath } = configForUpdate;
  if (key && typeof key === 'string') {
    config = _.omit(config, [key]);
  } else if (key && Array.isArray(key)) {
    config = _.omit(config, key);
  }
  storeConfig(config, configPath);
  return getConfig();
}

function get(path) {
  const config = getConfig();
  return _.get(config, path);
}

function getLoggedInUser() {
  const config = getConfig();
  if (!config.userId) {
    return null;
  }
  const user = _.get(config, ['users', config.userId, 'dashboard']);
  if (!user || !user.username) {
    return null; // user is logged out
  }
  return {
    userId: config.userId,
    username: user.username,
    accessKeys: user.accessKeys,
    idToken: user.idToken,
    refreshToken: user.refreshToken,
  };
}

module.exports = {
  set,
  get,
  delete: deleteValue,
  getConfig,
  getLoggedInUser,
  CONFIG_FILE_NAME: baseFilename,
};
