const Authentication = require('../modules/authentication');
const Mongoose = require('mongoose');
const Reset = require('./reset');

const UserSchema = new Mongoose.Schema({
	username: { type: String, required: true, unique: true },
	password: { type: String },
	name: { first: { type: String, required: true }, last: { type: String, required: true } },
	email: { type: String, required: true, unique: true },
	can: [{ type: String, enum: ['add','edit','delete','manage','super','update','view'], default: ['view'] }],
	forceReset: { type: Boolean, default: true },
	updated_at: { type: Date, default: Date.now }
});

UserSchema.pre('findOneAndUpdate', function (next) {
  var self = this;
  
  this.update({}, { $set: { updated_at: Date.now() } });

  if (this._update.$set.settings && this._update.$set.settings.length) {
      
  }

  if (this._update.$set.password && this._update.$set.confirmPassword && (this._update.$set.password == this._update.$set.confirmPassword)) {
    delete this._update.$set.confirmPassword;
    
    if (this._update.$set.currentPassword) {
      confirmPassword(this._conditions.userName, this._update.$set.currentPassword, (err, valid) => {
        if (err || !valid) {
          err = new Error({ success: false, message: 'There was an error validating the current password.', err: (err || null) });
          next(err);
        }
        
        if (valid) {
          delete this._update.$set.currentPassword; 
          hashPassword(this._update.$set.password, (err, hashedPassword) => {
            self.update({}, { $set: { password: hashedPassword } });
            self.update({}, { $set: { forceReset: false } });
            next();
          });    
        }
      });
    } else {
      hashPassword(this._update.$set.password, (err, hashedPassword) => {
        self.update({}, { $set: { password: hashedPassword } });
        self.update({}, { $set: { forceReset: false } });
        next();
      });    
    }
  } else if (this._update.$set.password && this._update.$set.confirmPassword && (this._update.$set.password != this._update.$set.confirmPassword)) {
    let err = new Error({ success: false, message: 'There was an error saving the updated password.'});
    next(err);
  } else if (!this._update.$set.password && !this._update.$set.confirmPassword) {
    next();
  }
});

UserSchema.post('save', function (err, res, next) {
  if (err.name === 'MongoError' && err.code === 11000) {
    next(new Error('There was a duplicate key error'));
  } else if (err) {
    next(err);
  } else {
    next();
  }
});

const UserModel = Mongoose.model('users', UserSchema);

var logger = require('../modules/logger'); 

function hashPassword (password, callback) {
  callback = callback || false;
  
  Authentication.hashPassword(password, (err, password) => {
    if (err !== null) {
      err = new Error({ success: false, message: 'There was an error hashing the updated password.', err: err });
      logger.error('[updateUser:hashPassword] ', err);
 
      if (callback) {
        callback(err, null);
      } else {
        return false;
      }
    }
    
    if (password) {
      var result = password.toString('hex');

      if (callback) {
        callback(null, result);
      } else {
        return result;
      }
    }
  });
}

function confirmPassword (username, passwordToValidate, callback) {
  callback = callback || false;
  
  UserModel.findById({ userName: username }, (err, user) => {
    if (err !== null) {
      err = new Error({ success: false, message: 'There was an error locating the user.', err: (err || null) });
      logger.error('[updateUser:confirmPassword] ', err);
 
      if (callback) {
        callback(err, null);
      } else {
        return false;
      }
    }
    
    if (user) {    
      Authentication.verifyPassword(passwordToValidate, Buffer.from(storedUser.password, 'hex'), (err, valid) => {
        if (err !== null || !valid) {
          err = new Error({ success: false, message: (!err && !valid ? 'The current password was incorrect.' : 'There was an error attempting to validate the password.'), err: (err || null) });
          logger.error('[updateUser:confirmPassword] ', { err: err, valid: valid });

          if (callback) {
            callback(err, null);
          } else {
            return false;
          }
        }
      
        if (valid) {
          if (callback) {
            callback(null, valid);
          } else {
            return true;
          }
        }
      });
    }  
  });
}

module.exports = {

  adminUpdatePassword: (e, username, password) => {
		const promise = new Promise((resolve, reject) => {
			UserModel.findOneAndUpdate({ userName: username }, { $set: password }, { new: true }, (err, result) => {
				if (err) {
					reject(err);
				}

				if (result) {
					resolve(result);
				}
			});
		});

		promise.then((result) => {
      if (e) {
  			e.emit('adminUpdatePassword', null, result);
  		} else {
  		  return result;
  		}
		})
		.catch((err) => {
		  if (e) {
  			e.emit('adminUpdatePassword', err, null);
  		} else {
  		  return err;
  		}
		});
  },

	authenticateUser: (e, login, headers) => {
		const promise = new Promise((resolve, reject) => {
		  var loginObject, user;
		
			UserModel
			  .findOne({ userName: login.userName }, 'username name email password can forceReset')
			  .exec((err, result) => {
          if (err) {
            loginObject = { 
              status: 200, 
              authorized: false, 
              err: { 
                id: '005',
                code: '[UMAU005]', 
                string: 'There was an error authenticating the user.'
              }
            }
            logger.debug('[UserModel::authenticateUser] Error finding user', { err: err, username: login.userName });
            resolve(loginObject);
          }

          if (result) {
            user = result;				

            if (user && !user.permission.disabled) {
              if (user.forceReset) {
                loginObject = { 
                  status: 200, 
                  authorized: false, 
                  err: { 
                    id: '003',
                    code: '[UMAU003]', 
                    string: 'A password reset has been mandated. Please check your email for a password reset link or request a new one from the login screen.'
                  }
                };
                resolve(loginObject);
              } 
              else {
                try {
                   Authentication.verifyPassword(login.password, Buffer.from(user.password, 'hex'), (err, valid) => {
                    if (err) {
                      logger.debug('[UserModel::authenticateUser] Error validating password', { err: err, user: user });
                      reject(err);
                    }

                    loginObject = {
                      status: 200,
                      authorized: valid
                    };

                    if (valid) {
                      loginObject.user = {
                        _id: user._id,
                        username: user.username,
                        name: user.name,
                        email: user.email,
                        can: user.can
                      };

                      loginObject.timestamp = Date.now();

                      logger.debug('[UserModel::authenticateUser] User Validated', { user: user, loginObject: loginObject });
                      resolve(loginObject);
                    } else {
                      loginObject.err = { 
                        id: '002',
                        code: '[UMAU002]',
                        string: 'The user id or password you entered was invalid.'
                      };

                      logger.debug('[UserModel::authenticateUser] Invalid Password', { user: user, loginObject: loginObject });
                      resolve(loginObject);
                    }
                  });
                } 
                catch (err) {
                  loginObject = { 
                    status: 200, 
                    authorized: false, 
                    err: { 
                      id: '004',
                      code: '[UMAU004]', 
                      string: 'There was an error authenticating the user, please contact an administrator.'
                    }
                  };

                  logger.error('[UserModel::authenticateUser] Error verifying password', { err: err, user: user });
                  resolve(loginObject);
                }
              }
            } 
            else if (user && user.permission.disabled) {
              loginObject = { 
                status: 200, 
                authorized: false, 
                err: { 
                  id: '000',
                  code: '[UMAU000]', 
                  string: 'The user is not authorized, please contact an administrator.'
                }
              };
              logger.debug('[UserModel::authenticateUser] The user is disabled', { err: err, user: user });
              resolve(loginObject);
            } 
            else {
              loginObject = { 
                status: 200, 
                authorized: false, 
                err: { 
                  id: '001',
                  code: '[UMAU001]', 
                  string: 'The user id or password you entered was invalid.'
                }
              };
              logger.debug('[UserModel::authenticateUser] The user does not exist', { err: err, user: user });
              resolve(loginObject);
            }
          }        
        });
    });

		promise.then((result) => {
			e.emit('authenticateUser', null, result);
		})
		.catch((err) => {
			e.emit('authenticateUser', err, null);
		});
	},

	createUser: (e, user) => {
		const promise = new Promise((resolve, reject) => {
      var userInstance = new UserModel(user);

      userInstance.save((err, result) => {
        logger.debug('createUser', { err: err, result: result, user: userInstance });
        
        if (err) {
          reject(err);
        }

        if (result) {
          Reset.sendNewUser(null, result);
          resolve(result);
        }
      });
		});

		promise.then((result) => {
			e.emit('createUser', null, result);
		})
		.catch((err) => {
			e.emit('createUser', err, null);
		});
	},
	
	deleteUser: (e, id) => {
		const promise = new Promise((resolve, reject) => {
			UserModel.remove({ _id: id }, (err, result) => {
				if (err) {
					reject(err);
				}

				if (result) {
					resolve(result);
				}
			});
		});

		promise.then((result) => {
			e.emit('deleteUser', null, result);
		})
		.catch((err) => {
			e.emit('deleteUser', err, null);
		});
	},

  findUser: (query, callback) => {
    UserModel.findOne(query, (err, result) => {
      if (err) {
        callback(err, null);
      }
      
      if (result) {
        callback(null, result);
      }
    });
  },

	forcePasswordReset: (e, id) => {
	  const promise = new Promise((resolve, reject) => {
      UserModel.findByIdAndUpdate(id, { $set: { forceReset: true } }, { new: true }, (err, result) => {
        if (err) {
          reject(err);
        }

        if (result) {
          let resetPromise = new Promise((resetResolve, resetReject) => {
            Reset.forceReset(null, result, (result) => {
              if (result.success) {
                resetResolve({ success: true, message: 'Force password reset initiated on the user.', result: result });
              } else {
                resetResolve({ success: false, message: 'There was an error initiating the forced password reset.', result: result });
              }
            });
          });
          
          resetPromise.then((result) => {
            resolve(result);
          })
          .catch((err) => {
            reject(err);
          });
        }
      });
	  });

		promise.then((result) => {
			e.emit('forcePasswordReset', null, result);
		})
		.catch((err) => {
			e.emit('forcePasswordReset', err, null);
		});	  
	},

	getUsers: (e, query, restricted = true) => {
    query = query || { find: {}, options: { sort: { 'name.last': 1, 'name.first': 1 }, limit: 0, skip: 0 }};

		const promise = new Promise((resolve, reject) => {
      var projection = 'userName name' + (restricted ? '' : ' title email permission avatar settings forceReset');
			var query = UserModel.find(query.find, projection, query.options);
				
			if (!restricted) {
				query.populate('permission', 'name disabled manageRoles manageUsers manageCategories manageAppPreferences deleteVendor addVendorTag addVendorSample addVendorComment editVendor addNewVendor viewPrivateDetails viewPublicDetails')
			}
			
			query.exec((err, results) => {
        if (err) {
          reject(err);
        }

        if (results) {
          resolve(results);
        }
      });
		});

		promise.then((results) => {
      if (e) {
  			e.emit('getUsers', null, results);
  		} else {
  		  return results;
  		}
		})
		.catch((err) => {
		  if (e) {
  			e.emit('getUsers', err, null);
  		} else {
  		  return err;
  		}
		});
	},

	getUser: (e, id, restricted = true) => {
		const promise = new Promise((resolve, reject) => {
      var projection = 'userName name' + (restricted ? '' : ' title email permission avatar settings forceReset');
			var query = UserModel
			  .findById(id)
			  .projection(projection);
        			  
			  if (!restricted) {
          query.populate('permission', 'name disabled manageRoles manageUsers manageCategories manageAppPreferences deleteVendor addVendorTag addVendorSample addVendorComment editVendor addNewVendor viewPrivateDetails viewPublicDetails');
			  }

			  query.exec((err, result) => {
          if (err) {
            reject(err);
          }

          if (result) {
            resolve(result);
          }
        });
		});

		promise.then((result) => {
      if (e) {
  			e.emit('getUser', null, result);
  		} else {
  		  return result;
  		}
		})
		.catch((err) => {
		  if (e) {
  			e.emit('getUser', err, null);
  		} else {
  		  return err;
  		}
		});
	},
	
	isUserNameUnique: (e, username) => {
	  const promise = new Promise((resolve, reject) => {
	    UserModel.findOne({ userName: username }, (err, result) => {
	      if (err) {
	        reject(err);
	      }
	      
	      if (result) {
	        resolve({ unique: false, length: true });
	      } else {
	        resolve({ unique: true, length: true });
	      }
	    });
	  });
	  
	  promise.then((result) => {
      e.emit('isUserNameUnique', null, result);
	  })
	  .catch((err) => {
      e.emit('isUserNameUnique', err, null);
	  });
	},
		
	updateUser: (e, id, user) => {
    const promise = new Promise((resolve, reject) => {
      UserModel
        .findByIdAndUpdate(id, { $set: user }, { new: true })
				.populate('permission', 'name disabled manageRoles manageUsers manageCategories manageAppPreferences deleteVendor addVendorTag addVendorSample addVendorComment editVendor addNewVendor viewPrivateDetails viewPublicDetails')
        .exec((err, result) => {
          if (err) {
            reject(err);
          }

          if (result) {
            resolve(result);
          }
        });
    });

    promise.then((result) => {
      e.emit('updateUser', null, result);
    })
    .catch((err) => {
      e.emit('updateUser', err, null);
    });
	},

	updatePassword: (e, id, token, data) => {
    const promise = new Promise((resolve, reject) => {
      Reset.checkReset(null, id, token, (err, validatedId) => {
        if (data.userId === validatedId) {
          UserModel.findByIdAndUpdate(data.userId, { $set: { password: data.password, confirmPassword: data.confirmPassword, forceReset: false } }, { new: true }, (err, updated) => {
            if (err) {
              if (err.success === false) {
                resolve(err);
              } else {
                reject(err);
              }
            }

            if (updated) {
              Reset.markUsed(null, id);
              resolve({ success: true, updated: updated });
            }
          });
        } else {
          resolve({ success: false, message: 'The password reset link is not valid. Please request a new link.'});
        }
      });
    
    });

		promise.then((result) => {
			e.emit('updatePassword', null, result);
		})
		.catch((err) => {
			e.emit('updatePassword', err, null);
		}); 
	}
};
