#!/usr/bin/env node
import chalk from 'chalk';
import table from 'text-table';
import minimist from 'minimist';
import * as cfg from '../lib/cfg';
import { handleError, error } from '../lib/error';
import NowSecrets from '../lib/secrets';
import ms from 'ms';

const argv = minimist(process.argv.slice(2), {
  string: ['config', 'token'],
  boolean: ['help', 'debug', 'base64'],
  alias: {
    help: 'h',
    config: 'c',
    debug: 'd',
    base64: 'b',
    token: 't'
  }
});
const subcommand = argv._[0];

// options
const help = () => {
  console.log(`
  ${chalk.bold('𝚫 now secrets')} <ls | add | rename | rm> <secret>

  ${chalk.dim('Options:')}

    -h, --help              output usage information
    -b, --base64            treat value as base64-encoded
    -c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline('FILE')}  config file
    -d, --debug             debug mode [off]
    -t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline('TOKEN')} login token

  ${chalk.dim('Examples:')}

  ${chalk.gray('–')} Lists all your secrets:

    ${chalk.cyan('$ now secrets ls')}

  ${chalk.gray('–')} Adds a new secret:

    ${chalk.cyan('$ now secrets add my-secret "my value"')}

    ${chalk.gray('–')} Once added, a secret's value can't be retrieved in plaintext anymore
    ${chalk.gray('–')} If the secret's value is more than one word, wrap it in quotes
    ${chalk.gray('–')} Actually, when in doubt, wrap your value in quotes

  ${chalk.gray('–')} Exposes a secret as an env variable:

    ${chalk.cyan(`$ now -e MY_SECRET=${chalk.bold('@my-secret')}`)}

    Notice the ${chalk.cyan.bold('`@`')} symbol which makes the value a secret reference.

  ${chalk.gray('–')} Renames a secret:

    ${chalk.cyan(`$ now secrets rename my-secret my-renamed-secret`)}

  ${chalk.gray('–')} Removes a secret:

    ${chalk.cyan(`$ now secrets rm my-secret`)}
`);
};

// options
const debug = argv.debug;
const apiUrl = argv.url || 'https://api.zeit.co';
if (argv.config) cfg.setConfigFile(argv.config);

const exit = (code) => {
  // we give stdout some time to flush out
  // because there's a node bug where
  // stdout writes are asynchronous
  // https://github.com/nodejs/node/issues/6456
  setTimeout(() => process.exit(code || 0), 100);
};

if (argv.help || !subcommand) {
  help();
  exit(0);
} else {
  const config = cfg.read();

  Promise.resolve(argv.token || config.token || login(apiUrl))
  .then(async (token) => {
    try {
      await run(token);
    } catch (err) {
      handleError(err);
      exit(1);
    }
  })
  .catch((e) => {
    error(`Authentication error – ${e.message}`);
    exit(1);
  });
}

async function run (token) {
  const secrets = new NowSecrets(apiUrl, token, { debug });
  const args = argv._.slice(1);
  const start = Date.now();

  if ('ls' === subcommand || 'list' === subcommand) {
    if (0 !== args.length) {
      error(`Invalid number of arguments. Usage: ${chalk.cyan('`now secret ls`')}`);
      return exit(1);
    }
    const list = await secrets.ls();
    const elapsed = ms(new Date() - start);
    console.log(`> ${list.length} secret${list.length > 1 ? 's' : ''} found ${chalk.gray(`[${elapsed}]`)}`);
    const cur = Date.now();
    const out = table(list.map((secret) => {
      return [
        '',
        secret.uid,
        chalk.bold(secret.name),
        chalk.gray(ms(cur - new Date(secret.created)) + ' ago')
      ];
    }), { align: ['l', 'r', 'l'], hsep: ' '.repeat(2) });
    if (out) console.log('\n' + out + '\n');
    return secrets.close();
  }

  if ('rm' === subcommand || 'remove' === subcommand) {
    if (1 !== args.length) {
      error(`Invalid number of arguments. Usage: ${chalk.cyan('`now secret rm <id | name>`')}`);
      return exit(1);
    }
    const list = await secrets.ls();
    const theSecret = list.filter((secret) => {
      return secret.uid === args[0]
          || secret.name === args[0];
    })[0];

    if (theSecret) {
      const yes = await readConfirmation(theSecret);
      if (!yes) {
        error('User abort');
        return exit(0);
      }
    } else {
      error(`No secret found by id or name "${args[0]}"`);
      return exit(1);
    }

    const secret = await secrets.rm(args[0]);
    const elapsed = ms(new Date() - start);
    console.log(`${chalk.cyan('> Success!')} Secret ${chalk.bold(secret.name)} ${chalk.gray(`(${secret.uid})`)} removed ${chalk.gray(`[${elapsed}]`)}`);
    return secrets.close();
  }

  if ('rename' === subcommand) {
    if (2 !== args.length) {
      error(`Invalid number of arguments. Usage: ${chalk.cyan('`now secret rename <old-name> <new-name>`')}`);
      return exit(1);
    }
    const secret = await secrets.rename(args[0], args[1]);
    const elapsed = ms(new Date() - start);
    console.log(`${chalk.cyan('> Success!')} Secret ${chalk.bold(secret.oldName)} ${chalk.gray(`(${secret.uid})`)} renamed to ${chalk.bold(args[1])} ${chalk.gray(`[${elapsed}]`)}`);
    return secrets.close();
  }

  if ('add' === subcommand || 'set' === subcommand) {
    if (2 !== args.length) {
      error(`Invalid number of arguments. Usage: ${chalk.cyan('`now secret add <name> <value>`')}`);
      if (args.length > 2) {
        const [, ...rest] = args;
        console.log('> If your secret has spaces, make sure to wrap it in quotes. Example: \n'
        + `  ${chalk.cyan('$ now secret add ${args[0]} "${escaped}"')} `);
      }
      return exit(1);
    }
    const [name, value_] = args;
    let value;
    if (argv.base64) {
      value = { base64: value_ };
    } else {
      value = value_;
    }
    const secret = await secrets.add(name, value);
    const elapsed = ms(new Date() - start);
    console.log(`${chalk.cyan('> Success!')} Secret ${chalk.bold(name)} ${chalk.gray(`(${secret.uid})`)} added ${chalk.gray(`[${elapsed}]`)}`);
    return secrets.close();
  }

  error('Please specify a valid subcommand: ls | add | rename | rm');
  help();
  exit(1);
}

process.on('uncaughtException', (err) => {
  handleError(err);
  exit(1);
});

function readConfirmation (secret) {
  return new Promise((resolve, reject) => {
    const time = chalk.gray(ms(new Date() - new Date(secret.created)) + ' ago');
    const tbl = table(
      [[secret.uid, chalk.bold(secret.name), time]],
      { align: ['l', 'r', 'l'], hsep: ' '.repeat(6) }
    );

    process.stdout.write('> The following secret will be removed permanently\n');
    process.stdout.write('  ' + tbl + '\n');

    process.stdout.write(`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[yN] ')}`);

    process.stdin.on('data', (d) => {
      process.stdin.pause();
      resolve('y' === d.toString().trim().toLowerCase());
    }).resume();
  });
}