text: const crypto = require('crypto');
  const { promisify } = require('util');
  const jwt = require('jsonwebtoken');
  const User = require('../models/userModel');
  const AppError = require('../utils/appError');
  const catchAsync = require('../utils/catchAsync');
  const Email = require('../utils/email');
  
  const signToken = id => {
    return jwt.sign({ id }, process.env.JWT_SECRET, {
      expiresIn: process.env.JWT_EXPIRES_IN,
    });
  };
  
  const createAndSendToken = (
    user,
    statusCode,
    res,
    data = false,
    message = ''
  ) => {
    const token = signToken(user._id);
  
    const cookieOptions = {
      expires: new Date(
        Date.now() + process.env.JWT_COOKIE_EXPIRES_IN * 24 * 60 * 60 * 1000
      ),
      httpOnly: true,
    };
  
    if (process.env.NODE_ENV === 'production') cookieOptions.secure = true;
  
    res.cookie('jwt', token, cookieOptions);
  
    if (data) {
      user.password = undefined;
    }
  
    res.status(statusCode).json({
      status: 'success',
      token,
      ...(data && { data: { user } }),
      ...(message && { message }),
    });
  };
  
  exports.signup = catchAsync(async (req, res, next) => {
    const newUser = await User.create({
      name: req.body.name,
      email: req.body.email,
      password: req.body.password,
      passwordConfirm: req.body.passwordConfirm,
      passwordChangedAt: req.body.passwordChangedAt,
      //role: req.body.role,
    });
  
    const url = `${req.protocol}://${req.get('host')}/me`;
  
    await new Email(newUser, url).sendWelcome();
  
    createAndSendToken(newUser, 201, res, true);
  });
  
  exports.login = catchAsync(async (req, res, next) => {
    const { email, password } = req.body;
  
    // 1) Check if emal and password exists
    if (!email || !password) {
      return next(new AppError('Please provide email or password', 400));
    }
  
    // 2) Check if user exists && password is correct
    const user = await User.findOne({ email }).select('+password');
  
    if (!user || !(await user.correctPassword(password, user.password))) {
      return next(new AppError('Incorrect email or password', 401));
    }
  
    // 3) If everything ok, send token to client
    createAndSendToken(user, 200, res, false, 'Login Successful');
  });
  
  exports.logout = (req, res) => {
    res.cookie('jwt', null, {
      expires: new Date(Date.now() + 10 * 1000),
      httpOnly: true,
    });
  
    res.status(200).json({ status: 'success' });
  };
  
  exports.protect = catchAsync(async (req, res, next) => {
    //protect routes middleware
    // 1) Getting token and check of it's there
    let token;
    if (
      req.headers.authorization &&
      req.headers.authorization.startsWith('Bearer')
    ) {
      token = req.headers.authorization.split(' ')[1];
    } else if (req.cookies.jwt) {
      token = req.cookies.jwt;
    }
  
    if (!token) {
      return next(
        new AppError('You are not login in! Please login to get the access.', 401)
      );
    }
    // 2) Verify token (If no one altered the ID of the payload)
    const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
  
    // 3) Check if user still exists: Prevent accessing to a deleted user token
    const freshUser = await User.findById(decoded.id);
  
    if (!freshUser) {
      return next(
        new AppError('The user belonging to the token does no longer exist!', 401)
      );
    }
  
    // 4) Check if user changed password after the token was issued
    if (freshUser.changedPasswordAfter(decoded.iat)) {
      return next(
        new AppError(
          'You recently changed your password! Please login again.',
          401
        )
      );
    }
  
    // grand access to protected routes
  
    req.user = freshUser;
    res.locals.user = freshUser;
  
    next();
  });
  
  exports.restrictTo = (...roles) => {
    return (req, res, next) => {
      //Roles i.e. ['admin', 'lead-guide]. roles='user'
      if (!roles.includes(req.user.role)) {
        return next(
          new AppError('You do not have permission to perform this action!', 403)
        );
      }
  
      //User have permission to perform the action
      next();
    };
  };
  
  exports.forgotPassword = catchAsync(async (req, res, next) => {
    // 1) get user based on Posted email
    const user = await User.findOne({ email: req.body.email });
  
    if (!user) {
      return next(new AppError('There is no user with that email address!', 404));
    }
  
    // 2) Generate the random reset token
    const resetToken = user.createPasswordResetToken();
  
    await user.save({ validateBeforeSave: false });
  
    // 3) send it to user's email
    try {
      const resetURL = `${req.protocol}://${req.get(
        'host'
      )}/api/v1/users/resetPassword/${resetToken}`;
  
      await new Email(user, resetURL).sendPasswordReset();
  
      res.status(200).json({
        status: 'success',
        message: 'Token sent to your email!',
      });
    } catch (err) {
      user.passwordResetToken = undefined;
      user.passwordResetExpires = undefined;
      await user.save({ validateBeforeSave: false });
  
      return next(
        new AppError(
          'There was an error sending the email. Please try again later.',
          500
        )
      );
    }
  });
  
  exports.resetPassword = catchAsync(async (req, res, next) => {
    // 1) Get user based on the token
    const hashedToken = crypto
      .createHash('sha256')
      .update(req.params.token)
      .digest('hex');
  
    const user = await User.findOne({
      passwordResetToken: hashedToken,
      passwordResetExpires: { $gt: Date.now() },
    });
  
    // 2) If token has not expired, and there is user, set the new password
    if (!user) {
      return next(new AppError('Token Invalid/Expired', 400));
    }
  
    user.password = req.body.password;
    user.passwordConfirm = req.body.passwordConfirm;
    user.passwordResetToken = undefined;
    user.passwordResetExpires = undefined;
    await user.save();
  
    // 3) Update changedPasswordAt property for the user
    createAndSendToken(user, 200, res);
  });
  
  exports.updatePassword = catchAsync(async (req, res, next) => {
    // 1) Get user from collection
    const user = await User.findById(req.user._id).select('+password');
  
    if (!user) {
      return next(new AppError('Cannot find the logged user.', 404));
    }
    // 2) Check if posted current password is correct
    const currentPassword = req.body.currentPassword;
  
    if (!(await user.correctPassword(currentPassword, user.password))) {
      return next(new AppError('Incorrect password', 401));
    }
  
    // 3) If ok, update password
    user.password = req.body.password;
    user.passwordConfirm = req.body.passwordConfirm;
  
    await user.save();
  
    // 4) Log user in, send JWT
    createAndSendToken(user, 200, res, false, 'Password updated');
  });
  
  // Only for rendered pages and there is no errors
  exports.isLoggedIn = async (req, res, next) => {
    if (req.cookies.jwt) {
      try {
        // 2) Verify token (If no one altered the ID of the payload)
        const decoded = await promisify(jwt.verify)(
          req.cookies.jwt,
          process.env.JWT_SECRET
        );
  
        // 3) Check if user still exists: Prevent accessing to a deleted user token
        const currentUser = await User.findById(decoded.id);
  
        if (!currentUser) {
          return next();
        }
  
        // 4) Check if user changed password after the token was issued
        if (currentUser.changedPasswordAfter(decoded.iat)) {
          return next();
        }
  
        // there is a logged in user
        res.locals.user = currentUser;
  
        return next();
      } catch (err) {
        return next();
      }
    }
  
    next();
  };
  