text: const jwt = require('jsonwebtoken')
  const Customer = require('../models/customer')
  const User = require('../models/user')
  const UserAccess = require('../models/userAccess')
  const ForgotPassword = require('../models/forgotPassword')
  const {
    encrypt,
    getIP,
    getBrowserInfo,
    getCountry,
    buildErrObject,
    handleError,
    emailExists,
    sendRegistrationEmailMessage,
    sendResetPasswordEmailMessage
  } = require('./base')
  const uuid = require('uuid')
  const { addHours } = require('date-fns')
  const { matchedData } = require('express-validator/filter')
  const HOURS_TO_BLOCK = 2
  const LOGIN_ATTEMPTS = 5
  
  /*********************
   * Private functions *
   *********************/
  
  const generateToken = user => {
    const obj = {
      _id: user
    }
    return encrypt(
      jwt.sign(obj, process.env.JWT_SECRET, {
        expiresIn: process.env.JWT_EXPIRATION
      })
    )
  }
  
  const setUserInfo = req => {
    const user = {
      _id: req._id,
      name: req.name,
      email: req.email,
      role: req.role,
      verified: req.verified
    }
    return user
  }
  
  const saveUserAccessAndReturnToken = async (req, user) => {
    return new Promise((resolve, reject) => {
      const userAccess = new UserAccess({
        email: user.email,
        ip: getIP(req),
        browser: getBrowserInfo(req),
        country: getCountry(req)
      })
      userAccess.save(err => {
        if (err) {
          reject(buildErrObject(422, err.message))
        }
        const userInfo = setUserInfo(user)
        // Returns data with access token
        resolve({
          token: generateToken(user._id),
          user: userInfo
        })
      })
    })
  }
  
  const blockUser = async user => {
    return new Promise((resolve, reject) => {
      user.blockExpires = addHours(new Date(), HOURS_TO_BLOCK)
      user.save((err, result) => {
        if (err) {
          reject(buildErrObject(422, err.message))
        }
        if (result) {
          resolve(buildErrObject(409, 'BLOCKED_USER'))
        }
      })
    })
  }
  
  const saveLoginAttemptsToDB = async user => {
    return new Promise((resolve, reject) => {
      user.save((err, result) => {
        if (err) {
          reject(buildErrObject(422, err.message))
        }
        if (result) {
          resolve(true)
        }
      })
    })
  }
  
  const checkPassword = async (password, user) => {
    return new Promise((resolve, reject) => {
      user.comparePassword(password, (err, isMatch) => {
        if (err) {
          reject(buildErrObject(422, err.message))
        }
        if (!isMatch) {
          resolve(false)
        }
        resolve(true)
      })
    })
  }
  
  const blockIsExpired = ({ loginAttempts, blockExpires }) =>
    loginAttempts > LOGIN_ATTEMPTS && blockExpires <= new Date()
  
  const checkLoginAttemptsAndBlockExpires = async user => {
    return new Promise((resolve, reject) => {
      // Let user try to login again after blockexpires, resets user loginAttempts
      if (blockIsExpired(user)) {
        user.loginAttempts = 0
        user.save((err, result) => {
          if (err) {
            reject(buildErrObject(422, err.message))
          }
          if (result) {
            resolve(true)
          }
        })
      } else {
        // User is not blocked, check password (normal behaviour)
        resolve(true)
      }
    })
  }
  
  const userIsBlocked = async user => {
    return new Promise((resolve, reject) => {
      if (user.blockExpires > new Date()) {
        reject(buildErrObject(409, 'BLOCKED_USER'))
      }
      resolve(true)
    })
  }
  
  const findUser = async email => {
    return new Promise((resolve, reject) => {
      User.findOne(
        {
          email
        },
        'password loginAttempts blockExpires name email role verified',
        (err, item) => {
          if (err) {
            reject(buildErrObject(422, err.message))
          }
          if (!item) {
            reject(buildErrObject(404, 'USER_DOES_NOT_EXISTS'))
          }
          resolve(item)
        }
      )
    })
  }
  
  const passwordsDoNotMatch = async user => {
    user.loginAttempts += 1
    await saveLoginAttemptsToDB(user)
    return new Promise((resolve, reject) => {
      if (user.loginAttempts <= LOGIN_ATTEMPTS) {
        resolve(buildErrObject(409, 'WRONG_PASSWORD'))
      } else {
        resolve(blockUser(user))
      }
      reject(buildErrObject(422, 'ERROR'))
    })
  }
  
  const registerUser = async req => {
    return new Promise((resolve, reject) => {
      const user = new User({
        name: req.name,
        email: req.email,
        password: req.password,
        verification: uuid.v4()
      })
      user.save((err, item) => {
        if (err) {
          reject(buildErrObject(422, err.message))
        }
        resolve(item)
      })
    })
  }
  
  const returnRegisterToken = (item, userInfo) => {
    userInfo.verification = item.verification
    return {
      token: generateToken(item._id),
      user: userInfo
    }
  }
  
  const verificationExists = async id => {
    return new Promise((resolve, reject) => {
      User.findOne(
        {
          verification: id,
          verified: false
        },
        (err, user) => {
          if (err) {
            reject(buildErrObject(422, err.message))
          }
          if (!user) {
            reject(buildErrObject(404, 'NOT_FOUND_OR_ALREADY_VERIFIED'))
          }
          resolve(user)
        }
      )
    })
  }
  
  const verifyUser = async user => {
    return new Promise((resolve, reject) => {
      user.verified = true
      user.save((err, item) => {
        if (err) {
          reject(buildErrObject(422, err.message))
        }
        resolve({
          email: item.email,
          verified: item.verified
        })
      })
    })
  }
  
  
  const checkCustomerExist = async id => {
    return new Promise((resolve, reject) => {
      Customer.findOne(
        {
          _id: id
        },
        (err, customer) => {
          if (err) {
            reject(buildErrObject(422, err.message))
          }
          if (!customer) {
            reject(buildErrObject(404, 'CUSTOMER_NOT_FOUND'))
          }
          resolve(customer)
        }
      )
    })
  }
  
  exports.verifyCustomer = async (req, userid, res) => {
    try {
      const customer = await checkCustomerExist(req.customerid)
      if (!customer) {
        return res.status(201).json(customer)
      } else {
          if (customer.userid == userid) {
            return customer
          } else {
            res.status(201).json(buildErrObject(400, 'CUSTOMER_NOT_FOUND_IN_YOUR_ACCESS'))
            return 
          }
      }
    } catch (error) {
      handleError(res, error)
    }
  }
  
  const markResetPasswordAsUsed = async (req, forgot) => {
    return new Promise((resolve, reject) => {
      forgot.used = true
      forgot.ipChanged = getIP(req)
      forgot.browserChanged = getBrowserInfo(req)
      forgot.countryChanged = getCountry(req)
      forgot.save((err, item) => {
        if (err) {
          reject(buildErrObject(422, err.message))
        }
        if (!item) {
          reject(buildErrObject(404, 'NOT_FOUND'))
        }
        resolve({
          msg: 'PASSWORD_CHANGED'
        })
      })
    })
  }
  
  const updatePassword = async (password, user) => {
    return new Promise((resolve, reject) => {
      user.password = password
      user.save((err, item) => {
        if (err) {
          reject(buildErrObject(422, err.message))
        }
        if (!item) {
          reject(buildErrObject(404, 'NOT_FOUND'))
        }
        resolve(item)
      })
    })
  }
  
  const findUserToResetPassword = async email => {
    return new Promise((resolve, reject) => {
      User.findOne(
        {
          email
        },
        (err, user) => {
          if (err) {
            reject(buildErrObject(422, err.message))
          }
          if (!user) {
            reject(buildErrObject(404, 'NOT_FOUND'))
          }
          resolve(user)
        }
      )
    })
  }
  
  const findForgotPassword = async id => {
    return new Promise((resolve, reject) => {
      ForgotPassword.findOne(
        {
          verification: id,
          used: false
        },
        (err, item) => {
          if (err) {
            reject(buildErrObject(422, err.message))
          }
          if (!item) {
            reject(buildErrObject(404, 'NOT_FOUND_OR_ALREADY_USED'))
          }
          resolve(item)
        }
      )
    })
  }
  
  const saveForgotPassword = async req => {
    return new Promise((resolve, reject) => {
      const forgot = new ForgotPassword({
        email: req.body.email,
        verification: uuid.v4(),
        ipRequest: getIP(req),
        browserRequest: getBrowserInfo(req),
        countryRequest: getCountry(req)
      })
      forgot.save((err, item) => {
        if (err) {
          reject(buildErrObject(422, err.message))
        }
        resolve(item)
      })
    })
  }
  
  const forgotPasswordResponse = item => {
    return {
      msg: 'RESET_EMAIL_SENT',
      verification: item.verification
    }
  }
  
  const checkPermissions = async (data, next) => {
    return new Promise((resolve, reject) => {
      User.findById(data.id, (err, result) => {
        if (err) {
          reject(buildErrObject(422, err.message))
        }
        if (!result) {
          reject(buildErrObject(404, 'NOT_FOUND'))
        }
        if (data.roles.indexOf(result.role) > -1) {
          return resolve(next())
        }
        return reject(buildErrObject(401, 'UNAUTHORIZED'))
      })
    })
  }
  
  /********************
   * Public functions *
   ********************/
  
  exports.login = async (req, res) => {
    try {
      const data = matchedData(req)
      const user = await findUser(data.email)
      await userIsBlocked(user)
      await checkLoginAttemptsAndBlockExpires(user)
      const isPasswordMatch = await checkPassword(data.password, user)
      if (!isPasswordMatch) {
        handleError(res, await passwordsDoNotMatch(user))
      } else {
        // all ok, register access and return token
        user.loginAttempts = 0
        await saveLoginAttemptsToDB(user)
        res.status(200).json(await saveUserAccessAndReturnToken(req, user))
      }
    } catch (error) {
      handleError(res, error)
    }
  }
  
  exports.register = async (req, res) => {
    try {
      req = matchedData(req)
      const doesEmailExists = await emailExists(req.email)
      if (!doesEmailExists) {
        const item = await registerUser(req)
        const userInfo = setUserInfo(item)
        const response = returnRegisterToken(item, userInfo)
        sendRegistrationEmailMessage(item)
        res.status(201).json(response)
      }
    } catch (error) {
      handleError(res, error)
    }
  }
  
  exports.verify = async (req, res) => {
    try {
      req = matchedData(req)
      const user = await verificationExists(req.id)
      res.status(200).json(await verifyUser(user))
    } catch (error) {
      handleError(res, error)
    }
  }
  
  exports.forgotPassword = async (req, res) => {
    try {
      const data = matchedData(req)
      await findUser(data.email)
      const item = await saveForgotPassword(req)
      sendResetPasswordEmailMessage(item)
      res.status(200).json(forgotPasswordResponse(item))
    } catch (error) {
      handleError(res, error)
    }
  }
  
  exports.resetPassword = async (req, res) => {
    try {
      const data = matchedData(req)
      const forgotPassword = await findForgotPassword(data.id)
      const user = await findUserToResetPassword(forgotPassword.email)
      await updatePassword(data.password, user)
      const result = await markResetPasswordAsUsed(req, forgotPassword)
      res.status(200).json(result)
    } catch (error) {
      handleError(res, error)
    }
  }
  
  exports.roleAuthorization = roles => async (req, res, next) => {
    try {
      const data = {
        id: req.user._id,
        roles
      }
      await checkPermissions(data, next)
    } catch (error) {
      handleError(res, error)
    }
  }