import express from 'express';
import { check, validationResult } from 'express-validator';
import path from 'path';
import fs from 'fs';
import Mustache from 'mustache';
import jwt from 'jsonwebtoken';
import { pick } from 'lodash';
import uniqid from 'uniqid';
import moment from 'moment';
import Logger from '@/services/Logger';
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
import SystemUser from '@/system/models/SystemUser';
import mail from '@/services/mail';
import { hashPassword } from '@/utils';
import dbManager from '@/database/manager';
import Tenant from '@/system/models/Tenant';
import TenantUser from '@/models/TenantUser';
import TenantsManager from '@/system/TenantsManager';
import TenantModel from '@/models/TenantModel';
import PasswordReset from '@/system/models/PasswordReset';


export default {
  /**
   * Constructor method.
   */
  router() {
    const router = express.Router();

    router.post('/login',
      this.login.validation,
      asyncMiddleware(this.login.handler));

    router.post('/register',
      this.register.validation,
      asyncMiddleware(this.register.handler));

    router.post('/send_reset_password',
      this.sendResetPassword.validation,
      asyncMiddleware(this.sendResetPassword.handler));

    router.post('/reset/:token',
      this.resetPassword.validation,
      asyncMiddleware(this.resetPassword.handler));

    return router;
  },

  /**
   * User login authentication request.
   */
  login: {
    validation: [
      check('crediential').exists().isEmail(),
      check('password').exists().isLength({ min: 5 }),
    ],
    async handler(req, res) {
      const validationErrors = validationResult(req);

      if (!validationErrors.isEmpty()) {
        return res.boom.badData(null, {
          code: 'validation_error', ...validationErrors,
        });
      }
      const form = { ...req.body };
      const { JWT_SECRET_KEY } = process.env;

      Logger.log('info', 'Someone trying to login.', { form });

      const user = await SystemUser.query()
        .withGraphFetched('tenant')
        .where('email', form.crediential)
        .orWhere('phone_number', form.crediential)
        .first();

      if (!user) {
        return res.boom.badRequest(null, {
          errors: [{ type: 'INVALID_DETAILS', code: 100 }],
        });
      }
      if (!user.verifyPassword(form.password)) {
        return res.boom.badRequest(null, {
          errors: [{ type: 'INVALID_DETAILS', code: 100 }],
        });
      }
      if (!user.active) {
        return res.boom.badRequest(null, {
          errors: [{ type: 'USER_INACTIVE', code: 110 }],
        });
      }
      const lastLoginAt = moment().format('YYYY/MM/DD HH:mm:ss');

      const updateTenantUser = TenantUser.tenant().query()
        .where('id', user.id)
        .update({ last_login_at: lastLoginAt });

      const updateSystemUser = SystemUser.query()
        .where('id', user.id)
        .update({ last_login_at: lastLoginAt });

      await Promise.all([updateTenantUser, updateSystemUser]);

      const token = jwt.sign(
        { email: user.email, _id: user.id },
        JWT_SECRET_KEY,
        { expiresIn: '1d' },
      );
      Logger.log('info', 'Logging success.', { form });

      return res.status(200).send({ token, user });
    },
  },

  /**
   * Registers a new organization.
   */
  register: {
    validation: [
      check('organization_name').exists().trim().escape(),
      check('first_name').exists().trim().escape(),
      check('last_name').exists().trim().escape(),
      check('email').exists().trim().escape(),
      check('phone_number').exists().trim().escape(),
      check('password').exists().trim().escape(),
      check('country').exists().trim().escape(),
    ],
    async handler(req, res) {
      const validationErrors = validationResult(req);

      if (!validationErrors.isEmpty()) {
        return res.boom.badData(null, {
          code: 'validation_error', ...validationErrors,
        });
      }
      const form = { ...req.body };
      Logger.log('info', 'Someone trying to register.', { form });

      const user = await SystemUser.query()
        .where('email', form.email)
        .orWhere('phone_number', form.phone_number)
        .first();

      const errorReasons = [];

      if (user && user.phoneNumber === form.phone_number) {
        errorReasons.push({ type: 'PHONE_NUMBER_EXISTS', code: 100 });
      }
      if (user && user.email === form.email) {
        errorReasons.push({ type: 'EMAIL_EXISTS', code: 200 });
      }
      if (errorReasons.length > 0) {
        return res.status(400).send({ errors: errorReasons });
      }
      const organizationId = uniqid();
      const tenantOrganization = await Tenant.query().insert({
        organization_id: organizationId,
      });

      const hashedPassword = await hashPassword(form.password);
      const userInsert = {
        ...pick(form, ['first_name', 'last_name', 'email', 'phone_number']),
        active: true,
      };
      const registeredUser = await SystemUser.query().insert({
        ...userInsert,
        password: hashedPassword,
        tenant_id: tenantOrganization.id,
      });
      await dbManager.createDb(`bigcapital_tenant_${organizationId}`);

      const tenantDb = TenantsManager.knexInstance(organizationId);
      await tenantDb.migrate.latest();

      TenantModel.knexBinded = tenantDb;

      await TenantUser.bindKnex(tenantDb).query().insert({
        ...userInsert,
        invite_accepted_at: moment().format('YYYY/MM/DD HH:mm:ss'),
      });
      Logger.log('info', 'New tenant has been created.', { organizationId });

      const filePath = path.join(global.rootPath, 'views/mail/Welcome.html');
      const template = fs.readFileSync(filePath, 'utf8');
      const rendered = Mustache.render(template, { ...form });
      const mailOptions = {
        to: userInsert.email,
        from: `${process.env.MAIL_FROM_NAME} ${process.env.MAIL_FROM_ADDRESS}`,
        subject: 'Welcome to Bigcapital',
        html: rendered,
      };
      mail.sendMail(mailOptions, (error) => {
        if (error) {
          Logger.log('error', 'Failed send welcome mail', { error, form });
          return;
        }
        Logger.log('info', 'User has been sent welcome email successfuly.', { form });
      });

      return res.status(200).send({
        organization_id: organizationId,
      });
    },
  },

  /**
   * Send reset password link via email or SMS.
   */
  sendResetPassword: {
    validation: [
      check('email').exists().isEmail(),
    ],
    // eslint-disable-next-line consistent-return
    async handler(req, res) {
      const validationErrors = validationResult(req);

      if (!validationErrors.isEmpty()) {
        return res.boom.badData(null, {
          code: 'validation_error', ...validationErrors,
        });
      }
      const form = { ...req.body };
      Logger.log('info', 'User trying to send reset password.', { form });

      const user = await SystemUser.query().where('email', form.email).first();

      if (!user) {
        return res.status(400).send({
          errors: [{ type: 'EMAIL.NOT.REGISTERED', code: 200 }],
        });
      }
      // Delete all stored tokens of reset password that associate to the give email.
      await PasswordReset.query()
        .where('email', form.email)
        .delete();

      const token = uniqid();
      const passwordReset = await PasswordReset.query()
        .insert({ email: form.email, token });

      const filePath = path.join(global.rootPath, 'views/mail/ResetPassword.html');
      const template = fs.readFileSync(filePath, 'utf8');
      const rendered = Mustache.render(template, {
        url: `${req.protocol}://${req.hostname}/reset/${passwordReset.token}`,
        first_name: user.firstName,
        last_name: user.lastName,
        // contact_us_email: config.contactUsMail,
      });

      const mailOptions = {
        to: user.email,
        from: `${process.env.MAIL_FROM_NAME} ${process.env.MAIL_FROM_ADDRESS}`,
        subject: 'Bigcapital - Password Reset',
        html: rendered,
      };
      mail.sendMail(mailOptions, (error) => {
        if (error) {
          Logger.log('error', 'Failed send reset password mail', { error, form });
          return;
        }
        Logger.log('info', 'User has been sent reset password email successfuly.', { form });
      });
      res.status(200).send({ email: passwordReset.email });
    },
  },

  /**
   * Reset password.
   */
  resetPassword: {
    validation: [
      check('password').exists().isLength({ min: 5 }).custom((value, { req }) => {
        if (value !== req.body.confirm_password) {
          throw new Error("Passwords don't match");
        } else {
          return value;
        }
      }),
    ],
    async handler(req, res) {
      const validationErrors = validationResult(req);

      if (!validationErrors.isEmpty()) {
        return res.boom.badData(null, {
          code: 'validation_error', ...validationErrors,
        });
      }
      Logger.log('info', 'User trying to reset password.');
      const { token } = req.params;
      const { password } = req.body;

      const tokenModel = await PasswordReset.query()
        .where('token', token)
        // .where('created_at', '>=', Date.now() - 3600000)
        .first();

      if (!tokenModel) {
        return res.boom.badRequest(null, {
          errors: [{ type: 'TOKEN_INVALID', code: 100 }],
        });
      }
      const user = await SystemUser.query()
        .where('email', tokenModel.email).first();

      if (!user) {
        return res.boom.badRequest(null, {
          errors: [{ type: 'USER_NOT_FOUND', code: 120 }],
        });
      }
      const hashedPassword = await hashPassword(password);

      await SystemUser.query()
        .where('email', tokenModel.email)
        .update({
          password: hashedPassword,
        });

      // Delete the reset password token.
      await PasswordReset.query().where('token', token).delete();
      Logger.log('info', 'User password has been reset successfully.');

      return res.status(200).send({});
    },
  },
};
