const crypto = require('crypto')
const nodemailer = require('nodemailer')
const bcrypt = require('bcryptjs')
const { body, validationResult } = require('express-validator')
const jwt = require('jsonwebtoken')
const User = require('../models/User')
const { secretKey } = require('../authentication')
const permissions = require('../plugins/permissions')
const { _sr } = require('../plugins/serverResponses')
const { SMS } = require('../plugins/SMS_Module')
const { notifyAdmin } = require('../WebSocket/controllers/admin_EventsController')
const { saveUserToArpa, updateUserOnArpa } = require('../ArpaWebservice.js')
const {
  res404,
  res500,
  checkValidations,
  removeWhiteSpaces,
  checkMobileNumber,
  normalizeMobileNumber,
  nameOptimizer,
  checkNationalCode,
  isPersian,
  isPersianWithDigits,
  generateRandomDigits
} = require('../plugins/controllersHelperFunctions')
const _faSr = _sr.fa

/// ///////////////////////////////////////////////////////////////// verify account functions
function sendConfirmationEmail(userId) {
  return new Promise(async (resolve, reject) => {
    try {
      const user = await User.findById(userId)
      if (!user) throw new Error('invalid id')

      const emailKey = generateRandomDigits(6)
      user.emailConfirmationKey = emailKey
      await user.save()

      /// //// email configs
      // const hostURL = 'www.asan-service.com'
      const transporter = nodemailer.createTransport({
        host: 'mail.asan-service.com',
        port: 587,
        secure: false, // true for 465, false for other ports
        auth: {
          user: 'confirmation@asan-service.com',
          pass: 'N7xNFZp*L_X{'
        },
        tls: {
          rejectUnauthorized: false
        }
      })

      const emailHTMLTemplate = `
											<div style="direction: rtl;background-color: #065495;overflow: hidden;">
											<div
												style="width: 70%;border-radius: 10px;margin: 150px auto;background: #fff;text-align: center;padding: 20px;box-shadow: 0 15px 30px #000;"
											>
												<h1 style="color: #000;font-size: 25px;margin-bottom: 20px;">
													<span> این ایمیل در وبسایت</span>
													<strong style="color: #065495"> آسان سرویس </strong>
													<span> ثبت شده است. </span>
												</h1>
												<p style="color: #000;">کد تایید آدرس ایمیل شما:</p>
												<div style="text-align: center;margin-top: 5px;">
													<p style="color: #065495;text-align: center;font-weight: bold;font-size: 20px;direction: ltr;">
														${emailKey}
													</p>
												</div>
											</div>
										</div>
										`

      // send mail with defined transport object
      await transporter.sendMail({
        from: '"AsanService" <confirmation@asan-service.com>', // sender address
        to: user.email, // list of receivers
        subject: 'تایید ایمیل',
        text: emailHTMLTemplate,
        html: emailHTMLTemplate
      })

      resolve()
    } catch (err) {
      reject(err)
    }
  })
}

function sendConfirmationSMS(userId) {
  return new Promise(async (resolve, reject) => {
    try {
      const user = await User.findById(userId)
      if (!user) throw new Error('invalid id')

      const mobileKey = generateRandomDigits(6)
      user.mobileConfirmationKey = mobileKey
      await user.save()

      const smsMsg = [
        'این شماره در وبسایت آسان سرویس جهت ارائه خدمات گارانتی ثبت گردیده.',
        ' کد تایید شماره همراه: ',
        mobileKey
      ]

      await SMS([user.mobile_number], smsMsg.join(''))

      resolve()
    } catch (err) {
      reject(err)
    }
  })
}

/// //////////////////////////////////////////////////////////////////// admin accounts
module.exports.register_admin = [
  [
    body('first_name')
      .notEmpty()
      .withMessage(_faSr.required.first_name)
      .bail()
      .isLength({ min: 2 })
      .withMessage(_faSr.min_char.min2),

    body('last_name')
      .notEmpty()
      .withMessage(_faSr.required.last_name)
      .bail()
      .isLength({ min: 2 })
      .withMessage(_faSr.min_char.min2),

    body('username')
      .notEmpty()
      .withMessage(_faSr.required.username)
      .bail()
      .isLength({ min: 4 })
      .withMessage(_faSr.min_char.min4)
      .custom((value, { req }) => {
        return User.findOne({ username: value }).then(admin => {
          if (admin && admin.username === value) return Promise.reject(_faSr.duplicated.username)
          else return true
        })
      }),

    body('password')
      .isLength({ min: 4 })
      .withMessage(_faSr.min_char.min4)
      .custom((value, { req }) => {
        if (value === req.body.password_confirmation) return true
        else return Promise.reject(_faSr.response.passwords_not_match)
      })
  ],
  checkValidations(validationResult),
  async (req, res) => {
    try {
      const { first_name, last_name, username, permissions, personalCode } = req.body

      const data = { first_name, last_name, username, permissions, personalCode }
      data._creator = req.user_id
      data.confirmed = true
      data.scope = ['admin']

      const salt = await bcrypt.genSalt(10)
      const hash = await bcrypt.hash(req.body.password, salt)
      data.password = hash

      const admin = new User(data)
      await admin.save()
      return res.json({ message: _faSr.response.success_save })
    } catch (err) {
      return res500(res, err)
    }
  }
]

module.exports.update_admin = [
  [
    body('first_name')
      .notEmpty()
      .withMessage(_faSr.required.first_name)
      .bail()
      .isLength({ min: 2 })
      .withMessage(_faSr.min_char.min2),

    body('last_name')
      .notEmpty()
      .withMessage(_faSr.required.last_name)
      .bail()
      .isLength({ min: 2 })
      .withMessage(_faSr.min_char.min2),

    body('username')
      .notEmpty()
      .withMessage(_faSr.required.username)
      .bail()
      .isLength({ min: 4 })
      .withMessage(_faSr.min_char.min4)
      .custom((value, { req }) => {
        return User.findOne({ username: value }).then(admin => {
          if (admin && admin.username === value && admin._id.toString() !== req.params.id)
            return Promise.reject(_faSr.duplicated.username)
          else return true
        })
      })
  ],
  checkValidations(validationResult),
  (req, res) => {
    const { username, first_name, last_name, permissions,personalCode } = req.body

    const data = { username, first_name, last_name, permissions,personalCode }
    data._creator = req.user_id

    User.findByIdAndUpdate(req.params.id, data, (err, oldData) => {
      if (err || !oldData) return res.status(404).json({ message: _faSr.not_found.user_id })
      return res.json({ message: _faSr.response.success_activation })
    })
  }
]

module.exports.get_admins = [
  (req, res) => {
    const regex = /admin/
    User.find({ scope: regex, private: false }, (err, admins) => {
      if (err) return res500(res, err)
      return res.json(admins)
    })
  }
]

module.exports.get_one_admin = [
  (req, res) => {
    User.findById(req.params.id)
      .select('first_name last_name username permissions _creator personalCode')
      .populate('_creator', 'first_name last_name')
      .exec((err, admin) => {
        if (err || !admin) return res.status(404).json({ message: _faSr.not_found.user_id })
        else return res.json(admin)
      })
  }
]

module.exports.delete_admin = [
  (req, res) => {
    User.findByIdAndDelete(req.params.id, (err, user) => {
      if (err || !user) return res.status(404).json({ message: _faSr.not_found.user_id })
      else return res.json({ message: _faSr.response.success_save })
    })
  }
]

module.exports.permissions = [
  (req, res) => {
    return res.json(permissions)
  }
]

/// //////////////////////////////////////////////////////////////////// user accounts

module.exports.register_user = [
  [
    body('first_name')
      .notEmpty()
      .withMessage(_faSr.required.first_name)
      .bail()
      .isLength({ min: 2 })
      .withMessage(_faSr.min_char.min2)
      .bail()
      .custom((value, { req }) => {
        if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
        if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
        else return true
      }),

    body('last_name')
      .notEmpty()
      .withMessage(_faSr.required.first_name)
      .bail()
      .isLength({ min: 2 })
      .withMessage(_faSr.min_char.min2)
      .bail()
      .custom((value, { req }) => {
        if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
        if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
        else return true
      }),

    body('national_code')
      .notEmpty()
      .withMessage(_faSr.required.national_code)
      .bail()
      .custom((value, { req }) => {
        if (checkNationalCode(value)) return true
        else return Promise.reject(_faSr.format.national_code)
      })
      .bail()
      .custom((value, { req }) => {
        const nationalCode = value.trim()
        return User.findOne({ national_code: nationalCode }).then(user => {
          if (user && user.national_code === nationalCode)
            return Promise.reject(new Error('کاربر با این کد ملی از قبل وجود دارد'))
          else return true
        })
      }),

    body('province_id').notEmpty().withMessage(_faSr.required.province),

    body('city_id').notEmpty().withMessage(_faSr.required.city),

    body('address')
      .notEmpty()
      .withMessage(_faSr.required.address)
      .bail()
      .custom((value, { req }) => {
        if (!isPersianWithDigits(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
        else return true
      }),

    body('postal_code')
      .notEmpty()
      .withMessage(_faSr.required.postal_code)
      .bail()
      .isNumeric()
      .withMessage(_faSr.format.number),

    body('email')
      .notEmpty()
      .withMessage(_faSr.required.email)
      .bail()
      .isEmail()
      .withMessage(_faSr.format.email)
      .bail()
      .custom((value, { req }) => {
        const email = value.trim().toLowerCase()
        return User.findOne({ email }).then(user => {
          if (user && user.email === email) return Promise.reject(_faSr.duplicated.email)
          else return true
        })
      }),

    body('tel_number')
      .notEmpty()
      .withMessage(_faSr.required.phone_number)
      .bail()
      .isNumeric()
      .withMessage(_faSr.format.phone_number),

    body('mobile_number')
      .notEmpty()
      .withMessage(_faSr.required.phone_number)
      .bail()
      .custom((value, { req }) => {
        const mobileNStatus = checkMobileNumber(value)
        if (!mobileNStatus) return Promise.reject(_faSr.format.phone_number)
        else return Promise.resolve()
      })
      .bail()
      .custom((value, { req }) => {
        const mobileNumber = value.trim()
        return User.findOne({ mobile_number: mobileNumber }).then(user => {
          if (user && user.mobile_number === mobileNumber)
            return Promise.reject(new Error('کاربر با این شماره موبایل از قبل وجود دارد'))
          else return true
        })
      }),

    body('password')
      .isLength({ min: 4 })
      .withMessage(_faSr.min_char.min4)
      .custom((value, { req }) => {
        if (value === req.body.password_confirmation) return true
        else return Promise.reject(_faSr.response.passwords_not_match)
      })
  ],
  checkValidations(validationResult),
  async (req, res) => {
    try {
      const {
        first_name,
        last_name,
        national_code,
        province_id,
        province_name,
        city_id,
        city_name,
        address,
        postal_code,
        tel_number,
        mobile_number,
        email,
        store_name,
        password
      } = req.body

      const data = {
        last_name,
        province_id,
        province_name,
        city_id,
        city_name,
        address,
        postal_code,
        tel_number,
        store_name
      }
      data.first_name = nameOptimizer(first_name)
      data.national_code = national_code.trim()
      data.mobile_number = normalizeMobileNumber(mobile_number)
      data.email = email.trim().toLowerCase()

      // hash user password and done
      const salt = await bcrypt.genSalt(10)
      const hash = await bcrypt.hash(password, salt)
      data.password = hash

      const user = new User(data)
      await user.save()
      await sendConfirmationEmail(user._id)
      await sendConfirmationSMS(user._id)
      return res.json({ message: _faSr.response.success_save })
    } catch (err) {
      const registerFailurMsg = 'مشکلی در ثبت نام پیش آمده، لطفا مجددا تلاش کنید'
      res500(res, registerFailurMsg)
    }
  }
]

module.exports.update_user = [
  [
    body('first_name')
      .notEmpty()
      .withMessage(_faSr.required.first_name)
      .bail()
      .isLength({ min: 2 })
      .withMessage(_faSr.min_char.min2)
      .bail()
      .custom((value, { req }) => {
        if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
        if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
        else return true
      }),

    body('last_name')
      .notEmpty()
      .withMessage(_faSr.required.first_name)
      .bail()
      .isLength({ min: 2 })
      .withMessage(_faSr.min_char.min2)
      .bail()
      .custom((value, { req }) => {
        if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
        if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
        else return true
      }),

    body('national_code')
      .notEmpty()
      .withMessage(_faSr.required.national_code)
      .bail()
      .custom((value, { req }) => {
        if (checkNationalCode(value)) return true
        else return Promise.reject(_faSr.format.national_code)
      })
      .custom((value, { req }) => {
        const nationalCode = value.trim()
        return User.findOne({ national_code: nationalCode }).then(user => {
          if (user && user.national_code === nationalCode && user._id.toString() !== req.body._id)
            return Promise.reject(new Error('کاربر با این کد ملی از قبل وجود دارد'))
          else return true
        })
      }),

    body('province_id').notEmpty().withMessage(_faSr.required.province),

    body('city_id').notEmpty().withMessage(_faSr.required.city),

    body('address')
      .notEmpty()
      .withMessage(_faSr.required.address)
      .bail()
      .custom((value, { req }) => {
        if (!isPersianWithDigits(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
        else return true
      }),

    body('postal_code')
      .notEmpty()
      .withMessage(_faSr.required.postal_code)
      .bail()
      .isNumeric()
      .withMessage(_faSr.format.number),

    body('tel_number')
      .notEmpty()
      .withMessage(_faSr.required.phone_number)
      .bail()
      .isNumeric()
      .withMessage(_faSr.format.phone_number),

    body('mobile_number')
      .notEmpty()
      .withMessage(_faSr.required.phone_number)
      .bail()
      .custom((value, { req }) => {
        const mobileNStatus = checkMobileNumber(value)
        if (!mobileNStatus) return Promise.reject(_faSr.format.phone_number)
        else return Promise.resolve()
      })
      .bail()
      .custom((value, { req }) => {
        const mobileNumber = value.trim()
        return User.findOne({ mobile_number: mobileNumber }).then(user => {
          if (user && user.mobile_number === mobileNumber && user._id.toString() !== req.body._id)
            return Promise.reject(new Error('کاربر با این شماره موبایل از قبل وجود دارد'))
          else return true
        })
      }),

    body('email')
      .notEmpty()
      .withMessage(_faSr.required.email)
      .bail()
      .isEmail()
      .withMessage(_faSr.format.email)
      .bail()
      .custom((value, { req }) => {
        const email = value.trim().toLowerCase()
        return User.findOne({ email }).then(user => {
          if (user && user.email === email && user._id.toString() !== req.body._id)
            return Promise.reject(_faSr.duplicated.email)
          else return true
        })
      })
  ],
  checkValidations(validationResult),
  async (req, res) => {
    const {
      first_name,
      last_name,
      national_code,
      province_id,
      province_name,
      city_id,
      city_name,
      address,
      postal_code,
      tel_number,
      mobile_number,
      email,
      store_name
    } = req.body

    const MongoData = {
      last_name,
      province_id,
      province_name,
      city_id,
      city_name,
      address,
      postal_code,
      tel_number,
      store_name
    }
    MongoData.first_name = nameOptimizer(first_name)
    MongoData.national_code = national_code.trim()
    MongoData.mobile_number = normalizeMobileNumber(mobile_number)
    MongoData.email = email.trim().toLowerCase()

    try {
      const { user_id } = req
      const user = await User.findById(user_id)
      if (!user) return res404(res, _faSr.not_found.user_id)

      // check if need to ferify email or mobile
      const newEmail = user.email !== MongoData.email
      const newMobile = user.mobile_number !== MongoData.mobile_number
      if (newEmail) user.email_confirmed = false
      if (newMobile) user.mobile_confirmed = false

      // save new info to arpa
      await updateUserOnArpa(MongoData, user.verity_businessID, 'verity')
      await updateUserOnArpa(MongoData, user.panatech_businessID, 'panatech')

      // save new info to user
      user.first_name = MongoData.first_name
      user.last_name = MongoData.last_name
      user.national_code = MongoData.national_code
      user.province_id = MongoData.province_id
      user.province_name = MongoData.province_name
      user.city_id = MongoData.city_id
      user.city_name = MongoData.city_name
      user.address = MongoData.address
      user.postal_code = MongoData.postal_code
      user.tel_number = MongoData.tel_number
      user.store_name = MongoData.store_name
      user.mobile_number = MongoData.mobile_number
      user.email = MongoData.email

      await user.save()

      // send confirmation key after user saved
      if (newEmail) await sendConfirmationEmail(user_id)
      if (newMobile) await sendConfirmationSMS(user_id)

      return res.json({ message: _faSr.response.success_save })
    } catch (e) {
      console.log('Arpa Error during update user --', e)
      return res500(res, _faSr.response.problem)
    }
  }
]

module.exports.update_user_password = [
  [
    body('password')
      .isLength({ min: 4 })
      .withMessage(_faSr.min_char.min4)
      .custom((value, { req }) => {
        if (value === req.body.password_confirmation) return true
        else return Promise.reject(_faSr.response.passwords_not_match)
      })
  ],
  checkValidations(validationResult),
  async (req, res) => {
    // hash user password and done
    try {
      const { _id, password } = req.body
      const { user_id } = req
      const salt = await bcrypt.genSalt(10)
      const hash = await bcrypt.hash(password, salt)
      const user = await User.findById(_id)
      if (!user) throw new Error(_faSr.not_found.user_id)
      user.password = hash
      if (user.scope.includes('admin')) user._creator = user_id
      await user.save()
      return res.json({ message: _faSr.response.success_save })
    } catch (err) {
      if (err) return res500(res, err)
    }
  }
]

/// // reset user password

module.exports.generate_reset_user_password_link = [
  [
    body('type')
      .notEmpty()
      .withMessage('choose type')
      .bail()
      .custom((value, { req }) => {
        if (value === 'email' || value === 'mobile') return Promise.resolve()
        else return Promise.reject(new Error('invalid type'))
      }),

    body('email')
      .if((value, { req }) => req.body.type === 'email')
      .notEmpty()
      .withMessage(_faSr.required.email)
      .bail()
      .isEmail()
      .withMessage(_faSr.format.email),

    body('mobile')
      .if((value, { req }) => req.body.type === 'mobile')
      .notEmpty()
      .withMessage(_faSr.required.phone_number)
      .bail()
      .custom((value, { req }) => {
        const mobileNStatus = checkMobileNumber(value)
        if (!mobileNStatus) return Promise.reject(_faSr.format.phone_number)
        else return Promise.resolve()
      })
  ],
  checkValidations(validationResult),
  async (req, res) => {
    try {
      const { type, email, mobile } = req.body
      const query = {}
      if (type === 'email') query.email = email.toLowerCase().trim()
      if (type === 'mobile') query.mobile_number = normalizeMobileNumber(mobile)

      const user = await User.findOne(query)
      if (!user) throw new Error('err')

      const resetPassKey = await crypto.randomBytes(20).toString('hex')
      user.resetPassToken = resetPassKey
      const hostURL = 'www.asan-service.com'

      if (type === 'email') {
        const transporter = nodemailer.createTransport({
          host: 'mail.asan-service.com',
          port: 587,
          secure: false, // true for 465, false for other ports
          auth: {
            user: 'resetpassword@asan-service.com',
            pass: '3e9atXQV+7a&'
          },
          tls: {
            rejectUnauthorized: false
          }
        })

        const emailHTMLTemplate = `
							<div style="direction: rtl;background-color: #065495;overflow: hidden;">
								<div
									style="width: 50%;border-radius: 10px;margin: 150px auto;background: #fff;text-align: center;padding: 20px;box-shadow: 0 15px 30px #000;"
								>
									<H1 style="color: #000;">برای بازیابی کلمه عبور حساب کاربری خود روی لینک زیر کلیک کنید.</H1>
									<div style="text-align: center;margin-top: 20px;">
										<a
											href="https://${hostURL}/auth/resetpass-newpass/${resetPassKey}"
											target="_blank"
											style="display: inline-block;padding: 15px;background: #065495;color: #fff;border-radius: 5px;text-decoration: none;"
											>بازیابی کلمه عبور</a
										>
										<p style="font-size: 10px;color: #b1b1b1;margin-top: 40px">
											چنانچه قادر به کلیلک روی لینک بالا نیستید آدرس زیر را در مرورگر خود باز کنید.
										</p>
										<p style="font-size: 10px;color: #b1b1b1;">https://${hostURL}/auth/resetpass-newpass/${resetPassKey}</p>
									</div>
								</div>
							</div>
						`

        // send mail with defined transport object
        await transporter.sendMail({
          from: '"AsanService" <resetpassword@asan-service.com>', // sender address
          to: email, // list of receivers
          subject: 'لینک بازیابی کلمه عبور',
          text: emailHTMLTemplate,
          html: emailHTMLTemplate
        })
      } else {
        const smsMsg = [
          ' لینک بازیابی کلمه عبور آسان سرویس: ',
          `https://${hostURL}/auth/resetpass-newpass/${resetPassKey}`
        ]
        await SMS([user.mobile_number], smsMsg.join(''))
      }

      await user.save()
      let responseMsg = 'لینک بازیابی برای شما ارسال شد'
      if (type === 'email')
        responseMsg =
          'لینک بازیابی برای شما ارسال شد، چنانچه قادر به مشاهده ایمیل در صندوق ورودی خود نیستید پوشه spam را چک کنید.'
      return res.json({ message: responseMsg })
    } catch (err) {
      let errMsg = _faSr.not_found.user_id
      if (err.code === 'EENVELOPE') errMsg = 'مشکلی در ارسال ایمیل پیش آمد لطفا دوباره تلاش کنید'
      return res404(res, errMsg)
    }
  }
]

module.exports.reset_password_using_token = [
  [
    body('password')
      .isLength({ min: 4 })
      .withMessage(_faSr.min_char.min4)
      .custom((value, { req }) => {
        if (value === req.body.password_confirmation) return true
        else return Promise.reject(_faSr.response.passwords_not_match)
      })
  ],
  checkValidations(validationResult),
  async (req, res) => {
    try {
      const { token, password } = req.body
      if (!token) throw new Error('err')
      const user = await User.findOne({ resetPassToken: token })
      if (!user) throw new Error('err')
      const salt = await bcrypt.genSalt(10)
      const hash = await bcrypt.hash(password, salt)
      user.password = hash
      user.token = null
      user.resetPassToken = undefined
      await user.save()
      return res.json({ message: _faSr.response.success_save })
    } catch (err) {
      return res404(res, _faSr.response.expired_reset_link)
    }
  }
]

/// // confirm email
module.exports.changeEmail = [
  [
    body('email')
      .notEmpty()
      .withMessage(_faSr.required.email)
      .bail()
      .isEmail()
      .withMessage(_faSr.format.email)
      .bail()
      .custom((value, { req }) => {
        const email = value.toLowerCase()
        return User.findOne({ email }).then(user => {
          if (user && user.email === email) return Promise.reject(_faSr.duplicated.email)
          else return true
        })
      })
  ],
  checkValidations(validationResult),
  async (req, res) => {
    try {
      const { user_id } = req
      const { email } = req.body
      const user = await User.findById(user_id)
      if (!user) throw new Error('invalid id')
      user.email = email
      user.email_confirmed = false
      await user.save()
      await sendConfirmationEmail(user_id)
      return res.json({ message: 'کد تایید برای شما ارسال شد' })
    } catch (err) {
      return res500(res, err)
    }
  }
]

module.exports.resendEmailConfirmation = [
  async (req, res) => {
    try {
      const { user_id } = req
      await sendConfirmationEmail(user_id)
      return res.json({ message: 'کد تایید برای شما ارسال شد' })
    } catch (err) {
      return res500(res, err)
    }
  }
]

module.exports.confirmEmail = [
  async (req, res) => {
    try {
      const { user_id } = req
      const { verificationKey } = req.body
      const user = await User.findById(user_id)
      if (!user) throw new Error('err')

      if (verificationKey?.trim() !== user.emailConfirmationKey) throw new Error('404')

      if (!user.confirmed) {
        user.confirmed = true

        const verityID = await saveUserToArpa(user, 'verity')
        const panatechID = await saveUserToArpa(user, 'panatech')

        user.verity_businessID = verityID.businessID
        user.verity_businessCode = verityID.businessCode

        user.panatech_businessID = panatechID.businessID
        user.panatech_businessCode = panatechID.businessCode
      }

      // save new info to arpa
      await updateUserOnArpa(user, user.verity_businessID, 'verity')
      await updateUserOnArpa(user, user.panatech_businessID, 'panatech')

      // update user info in mongoDB
      user.email_confirmed = true
      user.emailConfirmationKey = undefined
      await user.save()

      return res.json({ message: 'ایمل شما تایید شد.' })
    } catch (e) {
      if (e === '404') return res404(res, 'کد تایید صحیح نمیباشد.')
      else return res500(res, 'مشکلی در تایید ایمیل پیش آمده، لطفا دوباره تلاش کنید')
    }
  }
]

/// // confirm mobile
module.exports.changeMobile = [
  [
    body('mobile')
      .notEmpty()
      .withMessage(_faSr.required.phone_number)
      .bail()
      .custom((value, { req }) => {
        const mobileNStatus = checkMobileNumber(value)
        if (!mobileNStatus) return Promise.reject(_faSr.format.phone_number)
        else return Promise.resolve()
      })
      .bail()
      .custom((value, { req }) => {
        return User.findOne({ mobile_number: value }).then(user => {
          if (user && user.mobile_number === value)
            return Promise.reject(new Error('کاربر با این شماره موبایل از قبل وجود دارد'))
          else return true
        })
      })
  ],
  checkValidations(validationResult),
  async (req, res) => {
    try {
      const { user_id } = req
      const { mobile } = req.body
      const user = await User.findById(user_id)
      if (!user) throw new Error('invalid id')
      user.mobile_number = mobile
      user.mobile_confirmed = false
      await user.save()
      await sendConfirmationSMS(user_id)
      return res.json({ message: 'کد تایید برای شما پیامک شد' })
    } catch (err) {
      return res500(res, err)
    }
  }
]

module.exports.resendMobileConfirmation = [
  async (req, res) => {
    try {
      const { user_id } = req
      await sendConfirmationSMS(user_id)
      return res.json({ message: 'کد تایید برای شما پیامک شد' })
    } catch (err) {
      return res500(res, err)
    }
  }
]

module.exports.confirmMobile = [
  async (req, res) => {
    try {
      const { user_id } = req
      const { verificationKey } = req.body
      const user = await User.findById(user_id)
      if (!user) throw new Error('err')

      if (verificationKey?.trim() !== user.mobileConfirmationKey) throw new Error('404')

      if (!user.confirmed) {
        user.confirmed = true

        const verityID = await saveUserToArpa(user, 'verity')
        const panatechID = await saveUserToArpa(user, 'panatech')

        user.verity_businessID = verityID.businessID
        user.verity_businessCode = verityID.businessCode

        user.panatech_businessID = panatechID.businessID
        user.panatech_businessCode = panatechID.businessCode
      }

      // save new info to arpa
      await updateUserOnArpa(user, user.verity_businessID, 'verity')
      await updateUserOnArpa(user, user.panatech_businessID, 'panatech')

      // update user info in mongoDB
      user.mobile_confirmed = true
      user.mobileConfirmationKey = undefined
      await user.save()

      return res.json({ message: 'شماره شما تایید شد.' })
    } catch (e) {
      if (e === '404') return res404(res, 'کد تایید صحیح نمیباشد.')
      else return res500(res, 'مشکلی در تایید شماره پیش آمده، لطفا دوباره تلاش کنید')
    }
  }
]

/// //////////////////////////////////////////////////////////////////// user managemnet by admin
module.exports.get_all_users_for_admin = [
  (req, res) => {
    User.find(
      {
        scope: ['user'],
        confirmed: true,
        verity_businessID: { $ne: null },
        verity_businessCode: { $ne: null }
      },
      (err, data) => {
        if (err) return res500(res, err)
        return res.json(data)
      }
    )
  }
]

module.exports.get_one_user_for_admin = [
  (req, res) => {
    User.findById(req.params.id, (err, user) => {
      if (err || !user) return res404(res, err)
      if (!user.seenByAdmin) {
        user.seenByAdmin = true
        user.save(err => {
          if (err) console.log('get one user for admin - save seenByAdmin error :', err)
          notifyAdmin('updateNewCustomers')
        })
      }
      return res.json(user)
    })
  }
]

module.exports.change_verity_info_by_admin = [
  [
    body('verity_businessCode').notEmpty().withMessage('کد مشتری نباید خالی باشد'),

    body('verity_businessID').notEmpty().withMessage('آیدی مشتری نباید خالی باشد')
  ],
  checkValidations(validationResult),
  async (req, res) => {
    try {
      const user = await User.findById(req.params?.id)
      if (!user) return res404(res, 'invalid user id')
      if (!user.old_verity_businessID && !user.old_verity_businessCode) {
        user.old_verity_businessID = user.verity_businessID
        user.old_verity_businessCode = user.verity_businessCode
        await user.save()
      }
      user.verity_businessID = req.body.verity_businessID
      user.verity_businessCode = req.body.verity_businessCode
      await user.save()
      return res.json({ message: _faSr.response.success_save })
    } catch (err) {
      return res500(res, err)
    }
  }
]

module.exports.change_panatech_info_by_admin = [
  [
    body('panatech_businessCode').notEmpty().withMessage('کد مشتری نباید خالی باشد'),

    body('panatech_businessID').notEmpty().withMessage('آیدی مشتری نباید خالی باشد')
  ],
  checkValidations(validationResult),
  async (req, res) => {
    try {
      const user = await User.findById(req.params?.id)
      if (!user) return res404(res, 'invalid user id')
      if (!user.old_panatech_businessID && !user.old_panatech_businessCode) {
        user.old_panatech_businessID = user.panatech_businessID
        user.old_panatech_businessCode = user.panatech_businessCode
        await user.save()
      }
      user.panatech_businessID = req.body.panatech_businessID
      user.panatech_businessCode = req.body.panatech_businessCode
      await user.save()
      return res.json({ message: _faSr.response.success_save })
    } catch (err) {
      return res500(res, err)
    }
  }
]


module.exports.update_user_by_admin = [
  [
    body('first_name')
      .notEmpty()
      .withMessage(_faSr.required.first_name)
      .bail()
      .isLength({ min: 2 })
      .withMessage(_faSr.min_char.min2)
      .bail()
      .custom((value, { req }) => {
        if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
        if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
        else return true
      }),

    body('last_name')
      .notEmpty()
      .withMessage(_faSr.required.first_name)
      .bail()
      .isLength({ min: 2 })
      .withMessage(_faSr.min_char.min2)
      .bail()
      .custom((value, { req }) => {
        if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
        if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
        else return true
      }),

    body('national_code')
      .notEmpty()
      .withMessage(_faSr.required.national_code)
      .bail()
      .custom((value, { req }) => {
        if (checkNationalCode(value)) return true
        else return Promise.reject(_faSr.format.national_code)
      })
      .custom((value, { req }) => {
        const nationalCode = value.trim()
        return User.findOne({ national_code: nationalCode }).then(user => {
          if (user && user.national_code === nationalCode && user._id.toString() !== req.body._id)
            return Promise.reject(new Error('کاربر با این کد ملی از قبل وجود دارد'))
          else return true
        })
      }),

    body('province_id').notEmpty().withMessage(_faSr.required.province),

    body('city_id').notEmpty().withMessage(_faSr.required.city),

    body('address')
      .notEmpty()
      .withMessage(_faSr.required.address)
      .bail()
      .custom((value, { req }) => {
        if (!isPersianWithDigits(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
        else return true
      }),

    body('postal_code')
      .notEmpty()
      .withMessage(_faSr.required.postal_code)
      .bail()
      .isNumeric()
      .withMessage(_faSr.format.number),

    body('tel_number')
      .notEmpty()
      .withMessage(_faSr.required.phone_number)
      .bail()
      .isNumeric()
      .withMessage(_faSr.format.phone_number),

    body('mobile_number')
      .notEmpty()
      .withMessage(_faSr.required.phone_number)
      .bail()
      .custom((value, { req }) => {
        const mobileNStatus = checkMobileNumber(value)
        if (!mobileNStatus) return Promise.reject(_faSr.format.phone_number)
        else return Promise.resolve()
      })
      .bail()
      .custom((value, { req }) => {
        const mobileNumber = value.trim()
        return User.findOne({ mobile_number: mobileNumber }).then(user => {
          if (user && user.mobile_number === mobileNumber && user._id.toString() !== req.body._id)
            return Promise.reject(new Error('کاربر با این شماره موبایل از قبل وجود دارد'))
          else return true
        })
      }),

    body('email')
      .notEmpty()
      .withMessage(_faSr.required.email)
      .bail()
      .isEmail()
      .withMessage(_faSr.format.email)
      .bail()
      .custom((value, { req }) => {
        const email = value.trim().toLowerCase()
        return User.findOne({ email }).then(user => {
          if (user && user.email === email && user._id.toString() !== req.body._id)
            return Promise.reject(_faSr.duplicated.email)
          else return true
        })
      })
  ],
  checkValidations(validationResult),
  async (req, res) => {
    const {
      first_name,
      last_name,
      national_code,
      province_id,
      province_name,
      city_id,
      city_name,
      address,
      postal_code,
      tel_number,
      mobile_number,
      email,
      store_name
    } = req.body

    const MongoData = {
      last_name,
      province_id,
      province_name,
      city_id,
      city_name,
      address,
      postal_code,
      tel_number,
      store_name
    }
    MongoData.first_name = nameOptimizer(first_name)
    MongoData.national_code = national_code.trim()
    MongoData.mobile_number = normalizeMobileNumber(mobile_number)
    MongoData.email = email.trim().toLowerCase()

    try {
      const  user_id  = req.params.id
      const user = await User.findById(user_id)
      if (!user)  {
        return res404(res, _faSr.not_found.user_id)
      }


      // check if need to ferify email or mobile
      const newEmail = user.email !== MongoData.email
      const newMobile = user.mobile_number !== MongoData.mobile_number
      if (newEmail) user.email_confirmed = false
      if (newMobile) user.mobile_confirmed = false

      // save new info to arpa
      await updateUserOnArpa(MongoData, user.verity_businessID, 'verity')
      await updateUserOnArpa(MongoData, user.panatech_businessID, 'panatech')

      // save new info to user
      user.first_name = MongoData.first_name
      user.last_name = MongoData.last_name
      user.national_code = MongoData.national_code
      user.province_id = MongoData.province_id
      user.province_name = MongoData.province_name
      user.city_id = MongoData.city_id
      user.city_name = MongoData.city_name
      user.address = MongoData.address
      user.postal_code = MongoData.postal_code
      user.tel_number = MongoData.tel_number
      user.store_name = MongoData.store_name
      user.mobile_number = MongoData.mobile_number
      user.email = MongoData.email

   

      await user.save()

      // send confirmation key after user saved
      if (newEmail) await sendConfirmationEmail(user_id)
      if (newMobile) await sendConfirmationSMS(user_id)

      return res.json({ message: _faSr.response.success_save })
    } catch (e) {
      console.log('Arpa Error during update user --', e)
      return res500(res, _faSr.response.problem)
    }
  }
]


/// //////////////////////////////////////////////////////////////////// authentication

/// //////// Login

module.exports.webLogin = [
  [
    body('username')
      .isLength({ min: 4 })
      .withMessage(_faSr.min_char.min4)
      .bail()
      .if((value, { req }) => {
        return req.body.password && req.body.password.length >= 4
      })
      .custom((value, { req }) => {
        return User.findOne({
          $or: [{ email: value.toLowerCase().trim() }, { national_code: value }, { username: value }]
        }).then(user => {
          if (!user) return Promise.reject(_faSr.not_found.password)
          else return true
        })
      }),

    body('password')
      .notEmpty()
      .withMessage(_faSr.required.password)
      .bail()
      .isLength({ min: 4 })
      .withMessage(_faSr.min_char.min4),

    body('remember_me')
      .exists()
      .withMessage(_faSr.required.remember_me)
      .bail()
      .isBoolean()
      .withMessage(_faSr.format.boolean)
  ],
  checkValidations(validationResult),
  async (req, res) => {
    try {
      const { username, password } = req.body
      const user = await User.findOne({
        $or: [{ email: username.toLowerCase().trim() }, { national_code: username }, { username }]
      })
      if (!user) throw new Error('err')
      const passwordMatch = await bcrypt.compare(password, user.password)
      if (!passwordMatch) throw new Error('err')
      const token = await jwt.sign({ _id: user._id }, secretKey, {
        expiresIn: req.body.remember_me ? '30d' : '7d'
      })
      user.token = token
      await user.save()
      return res.json({ token })
    } catch (err) {
      return res.status(422).json({
        validation: { username: { msg: _faSr.not_found.password } }
      })
    }
  }
]

module.exports.appLogin = [
  [
    body('username')
      .isLength({ min: 4 })
      .withMessage(_faSr.min_char.min4)
      .bail()
      .if((value, { req }) => {
        return req.body.password && req.body.password.length >= 4
      })
      .custom((value, { req }) => {
        return User.findOne({
          $or: [{ email: value.toLowerCase().trim() }, { national_code: value }]
        }).then(user => {
          if (!user) return Promise.reject(_faSr.not_found.password)
          else return true
        })
      }),

    body('password')
      .notEmpty()
      .withMessage(_faSr.required.password)
      .bail()
      .isLength({ min: 4 })
      .withMessage(_faSr.min_char.min4)
  ],
  checkValidations(validationResult),
  async (req, res) => {
    try {
      const { username, password } = req.body
      const user = await User.findOne({
        $or: [{ email: username.toLowerCase().trim() }, { national_code: username }]
      })
      if (!user) throw new Error('err')

      const passwordMatch = await bcrypt.compare(password, user.password)
      if (!passwordMatch) throw new Error('err')

      const token = await jwt.sign({ _id: user._id }, secretKey, {
        expiresIn: '60d'
      })
      user.appToken = token
      await user.save()
      return res.json({ token })
    } catch (err) {
      return res.status(422).json({
        validation: { username: { msg: _faSr.not_found.password } }
      })
    }
  }
]

/// //////// logout
module.exports.webLogout = [
  async (req, res) => {
    try {
      const token = req.headers?.authorization
      if (!token) throw new Error('err')

      const user = await User.findOne({ token })
      if (!user) throw new Error('err')

      user.token = null
      await user.save()

      return res.json({ message: _faSr.response.logged_out })
    } catch (err) {
      return res.status(401).json({ message: _faSr.response.not_logged_in })
    }
  }
]

module.exports.appLogout = [
  async (req, res) => {
    try {
      const token = req.headers?.authorization
      if (!token) throw new Error('err')

      const user = await User.findOne({ appToken: token })
      if (!user) throw new Error('err')

      user.appToken = null
      await user.save()

      return res.json({ message: _faSr.response.logged_out })
    } catch (err) {
      return res.status(401).json({ message: _faSr.response.not_logged_in })
    }
  }
]

/// //////// get user
module.exports.getUser = [
  async (req, res) => {
    try {
      const token = req.headers?.authorization
      if (!token) throw new Error('err')

      const decoded = jwt.verify(token, secretKey)
      if (!decoded) throw new Error('err')

      const user = await User.findById(decoded?._id).select(
        '-token -appToken -emailConfirmationKey -mobileConfirmationKey -password -resetPassToken'
      )
      return res.json({ user })
    } catch (err) {
      console.log('🚀 ~ file: userController.js ~ line 1070 ~ err', err)
      return res.status(401).json({ message: 'unauthenticated' })
    }
  }
]


module.exports.updateUserByAdmin = [
  [
    body('first_name')
      .notEmpty()
      .withMessage(_faSr.required.first_name)
      .bail()
      .isLength({ min: 2 })
      .withMessage(_faSr.min_char.min2)
      .bail()
      .custom((value, { req }) => {
        if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
        if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
        else return true
      }),

    body('last_name')
      .notEmpty()
      .withMessage(_faSr.required.first_name)
      .bail()
      .isLength({ min: 2 })
      .withMessage(_faSr.min_char.min2)
      .bail()
      .custom((value, { req }) => {
        if (removeWhiteSpaces(value).length < 2) return Promise.reject(_faSr.min_char.min2)
        if (!isPersian(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
        else return true
      }),

    body('national_code')
      .notEmpty()
      .withMessage(_faSr.required.national_code)
      .bail()
      .custom((value, { req }) => {
        if (checkNationalCode(value)) return true
        else return Promise.reject(_faSr.format.national_code)
      })
      .custom((value, { req }) => {
        const nationalCode = value.trim()
        return User.findOne({ national_code: nationalCode }).then(user => {
          if (user && user.national_code === nationalCode && user._id.toString() !== req.body._id)
            return Promise.reject(new Error('کاربر با این کد ملی از قبل وجود دارد'))
          else return true
        })
      }),

    body('province_id').notEmpty().withMessage(_faSr.required.province),

    body('city_id').notEmpty().withMessage(_faSr.required.city),

    body('address')
      .notEmpty()
      .withMessage(_faSr.required.address)
      .bail()
      .custom((value, { req }) => {
        if (!isPersianWithDigits(value)) return Promise.reject(new Error('لطفا با حروف فارسی تایپ کنید'))
        else return true
      }),

    body('postal_code')
      .notEmpty()
      .withMessage(_faSr.required.postal_code)
      .bail()
      .isNumeric()
      .withMessage(_faSr.format.number),

    body('tel_number')
      .notEmpty()
      .withMessage(_faSr.required.phone_number)
      .bail()
      .isNumeric()
      .withMessage(_faSr.format.phone_number),

    body('mobile_number')
      .notEmpty()
      .withMessage(_faSr.required.phone_number)
      .bail()
      .custom((value, { req }) => {
        const mobileNStatus = checkMobileNumber(value)
        if (!mobileNStatus) return Promise.reject(_faSr.format.phone_number)
        else return Promise.resolve()
      })
      .bail()
      .custom((value, { req }) => {
        const mobileNumber = value.trim()
        return User.findOne({ mobile_number: mobileNumber }).then(user => {
          if (user && user.mobile_number === mobileNumber && user._id.toString() !== req.body._id)
            return Promise.reject(new Error('کاربر با این شماره موبایل از قبل وجود دارد'))
          else return true
        })
      }),

    body('email')
      .notEmpty()
      .withMessage(_faSr.required.email)
      .bail()
      .isEmail()
      .withMessage(_faSr.format.email)
      .bail()
      .custom((value, { req }) => {
        const email = value.trim().toLowerCase()
        return User.findOne({ email }).then(user => {
          if (user && user.email === email && user._id.toString() !== req.body._id)
            return Promise.reject(_faSr.duplicated.email)
          else return true
        })
      })
  ],
  checkValidations(validationResult),
  async (req, res) => {
    const {
      first_name,
      last_name,
      national_code,
      province_id,
      province_name,
      city_id,
      city_name,
      address,
      postal_code,
      tel_number,
      mobile_number,
      email,
      store_name
    } = req.body

    const MongoData = {
      last_name,
      province_id,
      province_name,
      city_id,
      city_name,
      address,
      postal_code,
      tel_number,
      store_name
    }
    MongoData.first_name = nameOptimizer(first_name)
    MongoData.national_code = national_code.trim()
    MongoData.mobile_number = normalizeMobileNumber(mobile_number)
    MongoData.email = email.trim().toLowerCase()

    try {
      const user_id  = req.params.id
      const user = await User.findById(user_id)
      if (!user) return res404(res, _faSr.not_found.user_id)

      // check if need to ferify email or mobile
      const newEmail = user.email !== MongoData.email
      const newMobile = user.mobile_number !== MongoData.mobile_number
      if (newEmail) user.email_confirmed = false
      if (newMobile) user.mobile_confirmed = false

      // save new info to arpa
      await updateUserOnArpa(MongoData, user.verity_businessID, 'verity')
      await updateUserOnArpa(MongoData, user.panatech_businessID, 'panatech')

      // save new info to user
      user.first_name = MongoData.first_name
      user.last_name = MongoData.last_name
      user.national_code = MongoData.national_code
      user.province_id = MongoData.province_id
      user.province_name = MongoData.province_name
      user.city_id = MongoData.city_id
      user.city_name = MongoData.city_name
      user.address = MongoData.address
      user.postal_code = MongoData.postal_code
      user.tel_number = MongoData.tel_number
      user.store_name = MongoData.store_name
      user.mobile_number = MongoData.mobile_number
      user.email = MongoData.email

      await user.save()

      // send confirmation key after user saved
      if (newEmail) await sendConfirmationEmail(user_id)
      if (newMobile) await sendConfirmationSMS(user_id)

      return res.json({ message: _faSr.response.success_save })
    } catch (e) {
      console.log('Arpa Error during update user --', e)
      return res500(res, _faSr.response.problem)
    }
  }
]
