#!/usr/bin/env node

/**
 * Module dependencies.
 */

const util = require('./util');
const program = require('commander');
const inquirer = require('inquirer');
const { stripIndent } = require('common-tags');
const Table = require('cli-table');

// Make things colorful!
require('colors');

// Register the autocomplete plugin.
inquirer.registerPrompt(
  'autocomplete',
  require('inquirer-autocomplete-prompt')
);

const Context = require('../graph/context');
const UsersService = require('../services/users');
const UserModel = require('../models/user');
const USER_ROLES = require('../models/enum/user_roles');
const mongoose = require('../services/mongoose');

// Register the shutdown criteria.
util.onshutdown([() => mongoose.disconnect()]);

/**
 * Deletes a user and cleans up their associated verifications.
 */
async function deleteUser(userID) {
  try {
    // Find the user we're removing.
    const user = await UserModel.findOne({ id: userID });
    if (!user) {
      throw new Error(`user with id ${userID} not found`);
    }

    printUserAsTable(user);

    console.warn(stripIndent`

    This will delete the above user.

    This might take a long time if there is a lot of data, please confirm that
    you want to continue.
    `);
    const { confirm } = await inquirer.prompt({
      type: 'confirm',
      name: 'confirm',
      message: 'Continue',
      default: false,
    });
    if (!confirm) {
      return util.shutdown();
    }

    const ctx = Context.forSystem();

    const { data, errors } = await ctx.graphql(
      `
      mutation DeleteUser($user_id: ID!) {
        delUser(id: $user_id) {
          errors {
            translation_key
          }
        }
      }
    `,
      { user_id: user.id }
    );
    if (errors) {
      throw errors;
    }

    if (data.errors) {
      throw data.errors;
    }

    console.log('User was deleted.');

    util.shutdown();
  } catch (err) {
    console.error(err);
    util.shutdown(1);
  }
}

function printUserAsTable(user) {
  let table = new Table({});

  table.push(
    { ID: user.id.gray },
    { Username: user.username },
    { Emails: user.emails },
    { Tags: user.tags ? user.tags.map(({ tag: { name } }) => name) : '' },
    { Role: user.role },
    { Verified: user.hasVerifiedEmail },
    { Username: user.status.username.status },
    { Banned: user.banned },
    {
      Suspension: user.suspended
        ? `Until ${user.status.suspension.until}`
        : false,
    }
  );

  console.log(table.toString());
}

/**
 * Searches for users based on their username and email address.
 */
async function searchUsers() {
  const ctx = Context.forSystem();
  const searchQuery = `
    query SearchUsers($value: String) {
      users(query: {value: $value}) {
        nodes {
          id
          username
          role
          profiles {
            id
            provider
          }
        }
      }
    }
  `;

  try {
    const answers = await inquirer.prompt({
      type: 'autocomplete',
      name: 'userID',
      message: 'Search for a user',
      source: async (answers, value) => {
        if (value === null) {
          value = '';
        }

        const { data, errors } = await ctx.graphql(searchQuery, {
          value,
        });
        if (errors && errors.length > 0) {
          throw errors[0];
        }

        if (data.users === null) {
          return [];
        }

        return data.users.nodes.map(user => {
          const emails = user.profiles
            .filter(({ provider }) => provider === 'local')
            .map(({ id }) => id)
            .join(', ');
          return {
            name: `${user.username} (${emails}) ${user.id.gray} - ${
              user.role.gray
            }`,
            value: user.id,
          };
        });
      },
    });

    const { userID } = answers;
    const user = await UserModel.findOne({ id: userID });

    printUserAsTable(user);
    util.shutdown(0);
  } catch (err) {
    console.error(err);
    util.shutdown(1);
  }
}

/**
 * Adds a role to a user
 * @param  {String} userUD id of the user to add the role to
 * @param  {String} role   the role to add
 */
async function setUserRole(userID) {
  try {
    const { role } = await inquirer.prompt([
      {
        name: 'role',
        message: 'User Role',
        type: 'list',
        choices: USER_ROLES,
      },
    ]);

    await UsersService.setRole(userID, role);

    console.log(`Set User ${userID} to the ${role} role.`);
    util.shutdown();
  } catch (err) {
    console.error(err);
    util.shutdown(1);
  }
}

/**
 * Verifies an email address for a user.
 *
 * @param userID the user's id
 * @param email  the user's email address to be verified, otherwise verifies the
 *               first email if there is one, if there are multiple, you get a
 *               prompt.
 */
async function verifyUserEmail(userID, email) {
  try {
    // Get the user.
    const user = await UserModel.findOne({ id: userID });
    if (!user) {
      throw new Error(`user with ID ${userID} cannot be found`);
    }

    // Get all the user's email addresses.
    const emails = user.emails;
    if (emails.length === 0) {
      throw new Error('user did not have any email addresses');
    }

    if (!email && emails.length === 1) {
      // The email wasn't passed, and there is only one option.
      email = emails[0];
    } else if (!emails.includes(email)) {
      // The email passed doesn't belong to this user.
      throw new Error(`user does not have the email ${email}`);
    } else if (emails.length > 1) {
      // The email wasn't passed, and there is more than one choice.
      const answers = await inquirer.prompt([
        {
          name: 'email',
          message: 'Select Email to Verify',
          type: 'list',
          choices: emails,
        },
      ]);

      email = answers.email;
    }

    // Verify the email.
    await UsersService.confirmEmail(userID, email);
    console.log(`User ${userID} had their email ${email} verified.`);
    util.shutdown();
  } catch (err) {
    console.error(err);
    util.shutdown(1);
  }
}

/**
 * createUser will prompt the user for the user information when creating a
 * local user.
 */
async function createUser() {
  try {
    const answers = await inquirer.prompt([
      {
        name: 'email',
        message: 'Email',
      },
      {
        name: 'username',
        message: 'Username',
      },
      {
        name: 'password',
        message: 'Password',
        type: 'password',
      },
      {
        name: 'role',
        message: 'Role',
        type: 'list',
        choices: USER_ROLES,
      },
    ]);

    const { email, username, password, role } = answers;

    // Create the user.
    const user = await UsersService.createLocalUser(email, password, username);

    // Set the role.
    await UsersService.setRole(user.id, role);

    console.log(`Created User[${user.id}]`);
    util.shutdown(0);
  } catch (err) {
    console.error(err);
    util.shutdown(1);
  }
}

//==============================================================================
// Setting up the program command line arguments.
//==============================================================================

program
  .command('create')
  .description('creates a local user')
  .action(createUser);

program
  .command('delete <userID>')
  .description('delete a user')
  .action(deleteUser);

program
  .command('list')
  .description('searches for a user based on their stored username and email')
  .action(searchUsers);

program
  .command('set-role <userID> <role>')
  .description('sets the role on a user')
  .action(setUserRole);

program
  .command('verify <userID> <email>')
  .description("verifies the given user's email address")
  .action(verifyUserEmail);

program.parse(process.argv);

// If there is no command listed, output help.
if (!process.argv.slice(2).length) {
  program.outputHelp();
  util.shutdown();
}
