import * as debugFactory from 'debug';
import * as nconf from 'nconf';
import * as path from 'path';
import * as _merge from 'lodash.merge';

// eslint-disable-next-line @typescript-eslint/no-require-imports
require('./nconf-truth');

const debug = debugFactory('snyk:config');

export type Json =
  | string
  | number
  | boolean
  | null
  | { [property: string]: Json }
  | Json[];

export interface Options {
  secretConfig?: string;
}

export function loadConfig(
  dir?: string,
  options?: Options,
): { [property: string]: Json } {
  if (!dir) {
    dir = '';
  }

  options = options || {};
  const secretConfig =
    options.secretConfig ||
    process.env['CONFIG_SECRET_FILE'] ||
    path.resolve(dir, 'config.secret.json');

  if (!path.isAbsolute(dir)) {
    throw new Error('config requires absolute path to read from');
  }

  const serviceEnv = process.env['SERVICE_ENV'];
  const localConfig = serviceEnv ? serviceEnv : 'local';
  const localConfigPath = path.resolve(dir, `config.${localConfig}.json`);
  debug('dir: %s, local: %s, secret: %s', dir, localConfigPath, secretConfig);

  const snykMatch = /^SNYK_.*$/;

  nconf.env({
    separator: '__',
    match: snykMatch,
    whitelist: ['NODE_ENV', 'PORT'],
  });
  nconf.argv();
  nconf.file('secret', { file: path.resolve(secretConfig) });
  nconf.file('local', { file: localConfigPath });
  nconf.file('default', { file: path.resolve(dir, 'config.default.json') });

  const config = nconf.get();

  // strip prefix from env vars in config
  Object.keys(config).forEach(function(key) {
    if (key.match(snykMatch)) {
      const trimmedKey = key.replace(/^SNYK_/, '');
      if (
        typeof config[trimmedKey] === 'object' &&
        typeof config[key] === 'object'
      ) {
        config[trimmedKey] = _merge(config[trimmedKey], config[key]);
      } else {
        config[trimmedKey] = config[key];
      }
      delete config[key];
    }
  });

  substituteEnvVarValues(config);

  debug('loading from %s', dir, JSON.stringify(config, null, 2));

  return config;
}

// recursively replace ${VAL} in config values with process.env.VAL
function substituteEnvVarValues(config): void {
  Object.keys(config).forEach(function(key) {
    // recurse through nested objects
    if (typeof config[key] === 'object') {
      return substituteEnvVarValues(config[key]);
    }

    // replace /\${.*?}/g in strings with env var if such exists
    if (typeof config[key] === 'string') {
      config[key] = config[key].replace(/(\${.*?})/g, function(_, match) {
        const val = match.slice(2, -1); // ditch the wrappers

        // explode if env var is missing
        if (process.env[val] === undefined) {
          throw new Error(
            'Missing env var to substitute ' +
              val +
              " in '" +
              key +
              ': "' +
              config[key] +
              '"\'',
          );
        }

        return process.env[val];
      });
    }
  });
}
