// dtp-sites-cli.js
// Copryright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0

'use strict';

const DTP_COMPONENT = { name: 'Sites CLI', slug: 'sites-cli' };

require('dotenv').config();

const path = require('path');

const mongoose = require('mongoose');
const generatePassword = require('password-generator');

const {
  SitePlatform,
  SiteAsync,
  SiteLog,
} = require(path.join(__dirname, 'lib', 'site-lib'));

module.rootPath = __dirname;
module.pkg = require(path.join(module.rootPath, 'package.json'));
module.config = {
  component: DTP_COMPONENT,
  root: module.rootPath,
  site: require(path.join(module.rootPath, 'config', 'site')),
  http: require(path.join(module.rootPath, 'config', 'http')),
};

module.log = new SiteLog(module, module.config.component);

module.grantPermission = async (target, permission) => {
  const User = mongoose.model('User');
  try {
    const user = await User.findOne({ email: target }).select('+permissions +flags');
    switch (permission) {
      case 'admin':
        user.flags.isAdmin = true;
        break;
      case 'moderator':
        user.flags.isModerator = true;
        break;
      case 'login':
        user.permissions.canLogin = true;
        break;
      default:
        throw new Error(`Invalid permission: ${permission}`);
    }
    await user.save();
    module.log.info('user updated', { user: user._id });
  } catch (error) {
    module.log.error('failed to grant permission', { app: module.app, target, permission, error });
  }
};

module.revokePermission = async (target, permission) => {
  const User = mongoose.model('User');
  try {
    const user = await User.findOne({ email: target }).select('+permissions +flags');
    switch (permission) {
      case 'admin':
        user.flags.isAdmin = false;
        break;
      case 'moderator':
        user.flags.isModerator = false;
        break;
      case 'login':
        user.permissions.canLogin = false;
        break;
      default:
        throw new Error(`Invalid permission: ${permission}`);
    }
    await user.save();
    module.log.info('user updated', { user: user._id });
  } catch (error) {
    module.log.error('failed to revoke permission', { app: module.app, target, permission, error });
  }
};

module.deleteOtpAccount = async (target) => {
  const { otpAuth: otpAuthService } = module.services;
  const User = mongoose.model('User');
  try {
    const user = await User.findOne({ email: target }).lean();
    const response = await otpAuthService.removeForUser(user);
    module.log.info('OTP accounts removed', { userId: user._id, response });
  } catch (error) {
    module.log.error('failed to remove OTP account', { target, error });
  }
};

module.makeBucket = async (target) => {
  const { minio: minioService } = module.services;
  try {
    module.log.info('creating bucket', { target });
    await minioService.makeBucket(target, 'us-east-1');
  } catch (error) {
    module.log.error('failed to create bucket', { target, error });
  }
};

module.resetIndexes = async ( ) => {
  try {
    module.log.info('resetting MongoDB indexes...');
    await SitePlatform.resetIndexes(module);
  } catch (error) {
    module.log.error('failed to reset database indexes', { error });
  }
};

module.resetPassword = async (username) => {
  const { user: userService } = module.services;
  const User = mongoose.model('User');

  const user = await User
    .findOne({
      username_lc: username.trim().toLowerCase(),
    })
    .lean();

  if (!user) {
    throw new Error('user not found');
  }

  const password = generatePassword(8, true);
  module.log.info('updating user password', { username, password });
  await userService.updatePassword(user, password);
};

module.sendWelcomeEmail = async (username) => {
  const User = mongoose.model('User');

  if (username === 'all') {
    await User
      .find()
      .select('+flags +optIn')
      .cursor()
      .eachAsync(module.sendUserWelcomeEmail, 4);
  } else {
    const user = await User
      .findOne({
        username_lc: username.trim().toLowerCase(),
      })
      .select('+flags +optIn')
      .lean();
    if (!user) {
      throw new Error('user not found');
    }
    await module.sendUserWelcomeEmail(user);
  }
};

module.sendUserWelcomeEmail = async (user) => {
  const { user: userService } = module.services;

  if (user.flags.isEmailVerified) {
    module.log.alert('user is email-verified (aboring welcome email)', {
      user: user._id,
      username: user.username,
    });
    return;
  }

  module.log.info('sending welcome email', {
    user: user._id,
    username: user.username,
    email: user.email,
  });
  await userService.sendWelcomeEmail(user);
};

module.requestConstellationConnect = async (host) => {
  const { coreNode: coreNodeService } = module.services;
  const { core, networkPolicy } = await coreNodeService.resolveCore(host);
  module.log.info('Constellation Connect is not implemented', { core, networkPolicy });
};

module.requestCoreConnect = async (host) => {
  const { coreNode: coreNodeService } = module.services;
  const { core, networkPolicy } = await coreNodeService.resolveCore(host);
  module.log.info('sending Core connection request', { core, networkPolicy });

  const txConnect = await coreNodeService.sendRequest(core, {
    method: 'POST',
    url: '/core/connect',
    body: {
      version: module.pkg.version,
      site: module.config.site,
    },
  });

  module.log.info('connect tranaction', { txConnect });
};

/*
 * SERVER INIT
 */

(async ( ) => {

  const argv = require('argv'); // https://www.npmjs.com/package/argv
  argv.version(module.pkg.version);
  argv.option([
    {
      name: 'action',
      short: 'a',
      type: 'string',
      description: 'The action to perform',
      example: 'sites-cli --action=grant --permission=moderator email email ...',
    },
    {
      name: 'permission',
      short: 'p',
      type: 'string',
      description: 'The permission(s) being added or removed',
      example: 'sites-cli --action=grant --permission=admin email email ...'
    },
  ]);

  try {
    await SitePlatform.startPlatform(module);
  } catch (error) {
    module.log.error(`failed to start DTP ${module.config.component.slug} platform`, { error });
    return;
  }
  
  try {
    module.app = argv.run();

    await SiteAsync.each(module.app.targets, async (target) => {
      module.log.info('processing target', { target });
      switch (module.app.options.action) {
        case 'grant':
          await module.grantPermission(target, module.app.options.permission);
          break;

        case 'revoke':
          await module.revokePermission(target, module.app.options.permission);
          break;
        
        case 'make-bucket':
          await module.makeBucket(target);
          break;

        case 'core-connect':
          await module.requestCoreConnect(target);
          break;

        case 'constellation-connect':
          await module.requestConstellationConnect(target);
          break;

        case 'delete-otp':
          await module.deleteOtpAccount(target);
          break;

        case 'reset-password':
          await module.resetPassword(target);
          break;

        case 'reset-indexes':
          await module.resetIndexes();
          break;

        case 'welcome-email':
          await module.sendWelcomeEmail(target);
          break;

        default:
          throw new Error(`invalid action: ${module.app.options.action}`);
      }
    });
    process.exit(0);
  } catch (error) {
    module.log.error('failed to process target', { app: module.app, error });
  }

})();