import { body } from 'express-validator';

import loggerFactory from '~/utils/logger';

import { listLocaleIds } from '~/utils/locale-utils';

import EditorSettings from '../../models/editor-settings';

const logger = loggerFactory('growi:routes:apiv3:personal-setting');

const express = require('express');

const passport = require('passport');

const router = express.Router();

/**
 * @swagger
 *  tags:
 *    name: PersonalSetting
 */

/**
 * @swagger
 *
 *  components:
 *    schemas:
 *      PersonalSettings:
 *        description: personal settings
 *        type: object
 *        properties:
 *          name:
 *            type: string
 *          email:
 *            type: string
 *          lang:
 *            type: string
 *          isEmailPublished:
 *            type: boolean
 *      Passwords:
 *        description: passwords for update
 *        type: object
 *        properties:
 *          oldPassword:
 *            type: string
 *          newPassword:
 *            type: string
 *          newPasswordConfirm:
 *            type: string
 *      AssociateUser:
 *        description: Ldap account for associate
 *        type: object
 *        properties:
 *          username:
 *            type: string
 *          password:
 *            type: string
 *      DisassociateUser:
 *        description: Ldap account for disassociate
 *        type: object
 *        properties:
 *          providerType:
 *            type: string
 *          accountId:
 *            type: string
 */
module.exports = (crowi) => {
  const accessTokenParser = require('../../middlewares/access-token-parser')(crowi);
  const loginRequiredStrictly = require('../../middlewares/login-required')(crowi);
  const csrf = require('../../middlewares/csrf')(crowi);
  const apiV3FormValidator = require('../../middlewares/apiv3-form-validator')(crowi);

  const { User, ExternalAccount } = crowi.models;

  const validator = {
    personal: [
      body('name').isString().not().isEmpty(),
      body('email').isEmail(),
      body('lang').isString().isIn(listLocaleIds()),
      body('isEmailPublished').isBoolean(),
    ],
    imageType: [
      body('isGravatarEnabled').isBoolean(),
    ],
    password: [
      body('oldPassword').isString(),
      body('newPassword').isString().not().isEmpty()
        .isLength({ min: 6 })
        .withMessage('password must be at least 6 characters long'),
      body('newPasswordConfirm').isString().not().isEmpty()
        .custom((value, { req }) => {
          return (value === req.body.newPassword);
        }),
    ],
    associateLdap: [
      body('username').isString().not().isEmpty(),
      body('password').isString().not().isEmpty(),
    ],
    disassociateLdap: [
      body('providerType').isString().not().isEmpty(),
      body('accountId').isString().not().isEmpty(),
    ],
    editorSettings: [
      body('textlintSettings.isTextlintEnabled').optional().isBoolean(),
      body('textlintSettings.textlintRules.*.name').optional().isString(),
      body('textlintSettings.textlintRules.*.options').optional(),
      body('textlintSettings.textlintRules.*.isEnabled').optional().isBoolean(),

    ],
  };

  /**
   * @swagger
   *
   *    /personal-setting:
   *      get:
   *        tags: [PersonalSetting]
   *        operationId: getPersonalSetting
   *        summary: /personal-setting
   *        description: Get personal parameters
   *        responses:
   *          200:
   *            description: params of personal
   *            content:
   *              application/json:
   *                schema:
   *                  properties:
   *                    currentUser:
   *                      type: object
   *                      description: personal params
   */
  router.get('/', accessTokenParser, loginRequiredStrictly, async(req, res) => {
    const { username } = req.user;

    try {
      const user = await User.findUserByUsername(username);

      // return email and apiToken
      const { email, apiToken } = user;
      const currentUser = user.toObject();
      currentUser.email = email;
      currentUser.apiToken = apiToken;

      return res.apiv3({ currentUser });
    }
    catch (err) {
      logger.error(err);
      return res.apiv3Err('update-personal-settings-failed');
    }
  });

  /**
   * @swagger
   *
   *    /personal-setting/is-password-set:
   *      get:
   *        tags: [PersonalSetting]
   *        operationId: getIsPasswordSet
   *        summary: /personal-setting
   *        description: Get whether a password has been set
   *        responses:
   *          200:
   *            description: Whether a password has been set
   *            content:
   *              application/json:
   *                schema:
   *                  properties:
   *                    isPasswordSet:
   *                      type: boolean
   */
  router.get('/is-password-set', accessTokenParser, loginRequiredStrictly, async(req, res) => {
    const { username } = req.user;

    try {
      const user = await User.findUserByUsername(username);
      const isPasswordSet = user.isPasswordSet();
      return res.apiv3({ isPasswordSet });
    }
    catch (err) {
      logger.error(err);
      return res.apiv3Err('fail-to-get-whether-password-is-set');
    }

  });

  /**
   * @swagger
   *
   *    /personal-setting:
   *      put:
   *        tags: [PersonalSetting]
   *        operationId: updatePersonalSetting
   *        summary: /personal-setting
   *        description: Update personal setting
   *        requestBody:
   *          required: true
   *          content:
   *            application/json:
   *              schema:
   *                $ref: '#/components/schemas/PersonalSettings'
   *        responses:
   *          200:
   *            description: params of personal
   *            content:
   *              application/json:
   *                schema:
   *                  properties:
   *                    currentUser:
   *                      type: object
   *                      description: personal params
   */
  router.put('/', accessTokenParser, loginRequiredStrictly, csrf, validator.personal, apiV3FormValidator, async(req, res) => {

    try {
      const user = await User.findOne({ _id: req.user.id });
      user.name = req.body.name;
      user.email = req.body.email;
      user.lang = req.body.lang;
      user.isEmailPublished = req.body.isEmailPublished;

      const updatedUser = await user.save();
      req.i18n.changeLanguage(req.body.lang);
      return res.apiv3({ updatedUser });
    }
    catch (err) {
      logger.error(err);
      return res.apiv3Err('update-personal-settings-failed');
    }

  });

  /**
   * @swagger
   *
   *    /personal-setting/image-type:
   *      put:
   *        tags: [PersonalSetting]
   *        operationId: putUserImageType
   *        summary: /personal-setting/image-type
   *        description: Update user image type
   *        responses:
   *          200:
   *            description: succeded to update user image type
   *            content:
   *              application/json:
   *                schema:
   *                  properties:
   *                    userData:
   *                      type: object
   *                      description: user data
   */
  router.put('/image-type', accessTokenParser, loginRequiredStrictly, csrf, validator.imageType, apiV3FormValidator, async(req, res) => {
    const { isGravatarEnabled } = req.body;

    try {
      const userData = await req.user.updateIsGravatarEnabled(isGravatarEnabled);
      return res.apiv3({ userData });
    }
    catch (err) {
      logger.error(err);
      return res.apiv3Err('update-personal-settings-failed');
    }
  });

  /**
   * @swagger
   *
   *    /personal-setting/external-accounts:
   *      get:
   *        tags: [PersonalSetting]
   *        operationId: getExternalAccounts
   *        summary: /personal-setting/external-accounts
   *        description: Get external accounts that linked current user
   *        responses:
   *          200:
   *            description: external accounts
   *            content:
   *              application/json:
   *                schema:
   *                  properties:
   *                    externalAccounts:
   *                      type: object
   *                      description: array of external accounts
   */
  router.get('/external-accounts', accessTokenParser, loginRequiredStrictly, async(req, res) => {
    const userData = req.user;

    try {
      const externalAccounts = await ExternalAccount.find({ user: userData });
      return res.apiv3({ externalAccounts });
    }
    catch (err) {
      logger.error(err);
      return res.apiv3Err('get-external-accounts-failed');
    }

  });

  /**
   * @swagger
   *
   *    /personal-setting/password:
   *      put:
   *        tags: [PersonalSetting]
   *        operationId: putUserPassword
   *        summary: /personal-setting/password
   *        description: Update user password
   *        requestBody:
   *          required: true
   *          content:
   *            application/json:
   *              schema:
   *                $ref: '#/components/schemas/Passwords'
   *        responses:
   *          200:
   *            description: user password
   *            content:
   *              application/json:
   *                schema:
   *                  properties:
   *                    userData:
   *                      type: object
   *                      description: user data updated
   */
  router.put('/password', accessTokenParser, loginRequiredStrictly, csrf, validator.password, apiV3FormValidator, async(req, res) => {
    const { body, user } = req;
    const { oldPassword, newPassword } = body;

    if (user.isPasswordSet() && !user.isPasswordValid(oldPassword)) {
      return res.apiv3Err('wrong-current-password', 400);
    }
    try {
      const userData = await user.updatePassword(newPassword);
      return res.apiv3({ userData });
    }
    catch (err) {
      logger.error(err);
      return res.apiv3Err('update-password-failed');
    }

  });

  /**
   * @swagger
   *
   *    /personal-setting/api-token:
   *      put:
   *        tags: [PersonalSetting]
   *        operationId: putUserApiToken
   *        summary: /personal-setting/api-token
   *        description: Update user api token
   *        responses:
   *          200:
   *            description: succeded to update user api token
   *            content:
   *              application/json:
   *                schema:
   *                  properties:
   *                    userData:
   *                      type: object
   *                      description: user data
   */
  router.put('/api-token', loginRequiredStrictly, csrf, async(req, res) => {
    const { user } = req;

    try {
      const userData = await user.updateApiToken();
      return res.apiv3({ userData });
    }
    catch (err) {
      logger.error(err);
      return res.apiv3Err('update-api-token-failed');
    }

  });

  /**
   * @swagger
   *
   *    /personal-setting/associate-ldap:
   *      put:
   *        tags: [PersonalSetting]
   *        operationId: associateLdapAccount
   *        summary: /personal-setting/associate-ldap
   *        description: associate Ldap account
   *        requestBody:
   *          required: true
   *          content:
   *            application/json:
   *              schema:
   *                $ref: '#/components/schemas/AssociateUser'
   *        responses:
   *          200:
   *            description: succeded to associate Ldap account
   *            content:
   *              application/json:
   *                schema:
   *                  properties:
   *                    associateUser:
   *                      type: object
   *                      description: Ldap account associate to me
   */
  router.put('/associate-ldap', accessTokenParser, loginRequiredStrictly, csrf, validator.associateLdap, apiV3FormValidator, async(req, res) => {
    const { passportService } = crowi;
    const { user, body } = req;
    const { username } = body;

    if (!passportService.isLdapStrategySetup) {
      logger.error('LdapStrategy has not been set up');
      return res.apiv3Err('associate-ldap-account-failed', 405);
    }

    try {
      await passport.authenticate('ldapauth');
      const associateUser = await ExternalAccount.associate('ldap', username, user);
      return res.apiv3({ associateUser });
    }
    catch (err) {
      logger.error(err);
      return res.apiv3Err('associate-ldap-account-failed');
    }

  });

  /**
   * @swagger
   *
   *    /personal-setting/disassociate-ldap:
   *      put:
   *        tags: [PersonalSetting]
   *        operationId: disassociateLdapAccount
   *        summary: /personal-setting/disassociate-ldap
   *        description: disassociate Ldap account
   *        requestBody:
   *          required: true
   *          content:
   *            application/json:
   *              schema:
   *                $ref: '#/components/schemas/DisassociateUser'
   *        responses:
   *          200:
   *            description: succeded to disassociate Ldap account
   *            content:
   *              application/json:
   *                schema:
   *                  properties:
   *                    disassociateUser:
   *                      type: object
   *                      description: Ldap account disassociate to me
   */
  router.put('/disassociate-ldap', accessTokenParser, loginRequiredStrictly, csrf, validator.disassociateLdap, apiV3FormValidator, async(req, res) => {
    const { user, body } = req;
    const { providerType, accountId } = body;

    try {
      const count = await ExternalAccount.count({ user });
      // make sure password set or this user has two or more ExternalAccounts
      if (user.password == null && count <= 1) {
        return res.apiv3Err('disassociate-ldap-account-failed');
      }
      const disassociateUser = await ExternalAccount.findOneAndRemove({ providerType, accountId, user });
      return res.apiv3({ disassociateUser });
    }
    catch (err) {
      logger.error(err);
      return res.apiv3Err('disassociate-ldap-account-failed');
    }

  });

  /**
   * @swagger
   *
   *    /personal-setting/editor-settings:
   *      put:
   *        tags: [EditorSetting]
   *        operationId: putEditorSettings
   *        summary: /editor-setting
   *        description: Put editor preferences
   *        responses:
   *          200:
   *            description: params of editor settings
   *            content:
   *              application/json:
   *                schema:
   *                  properties:
   *                    currentUser:
   *                      type: object
   *                      description: editor settings
   */
  router.put('/editor-settings', accessTokenParser, loginRequiredStrictly, csrf, validator.editorSettings, apiV3FormValidator, async(req, res) => {
    const query = { userId: req.user.id };
    const textlintSettings = req.body.textlintSettings;
    const document = {};

    if (textlintSettings == null) {
      return res.apiv3Err('no-settings-found');
    }

    if (textlintSettings.isTextlintEnabled != null) {
      Object.assign(document, { 'textlintSettings.isTextlintEnabled': textlintSettings.isTextlintEnabled });
    }
    if (textlintSettings.textlintRules != null) {
      Object.assign(document, { 'textlintSettings.textlintRules': textlintSettings.textlintRules });
    }

    // Insert if document does not exist, and return new values
    // See: https://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate
    const options = { upsert: true, new: true };
    try {
      const response = await EditorSettings.findOneAndUpdate(query, { $set: document }, options);
      return res.apiv3(response);
    }
    catch (err) {
      logger.error(err);
      return res.apiv3Err('updating-editor-settings-failed');
    }
  });


  /**
   * @swagger
   *
   *    /personal-setting/editor-settings:
   *      get:
   *        tags: [EditorSetting]
   *        operationId: getEditorSettings
   *        summary: /editor-setting
   *        description: Get editor preferences
   *        responses:
   *          200:
   *            description: params of editor settings
   *            content:
   *              application/json:
   *                schema:
   *                  properties:
   *                    currentUser:
   *                      type: object
   *                      description: editor settings
   */
  router.get('/editor-settings', accessTokenParser, loginRequiredStrictly, async(req, res) => {
    try {
      const query = { userId: req.user.id };
      const response = await EditorSettings.findOne(query);
      return res.apiv3(response);
    }
    catch (err) {
      logger.error(err);
      return res.apiv3Err('getting-editor-settings-failed');
    }
  });

  return router;
};
