text: var _                = require('lodash'),
      dataProvider     = require('../models'),
      settings         = require('./settings'),
      mail             = require('./mail'),
      globalUtils      = require('../utils'),
      utils            = require('./utils'),
      Promise          = require('bluebird'),
      errors           = require('../errors'),
      config           = require('../config'),
      authentication;
  
  function setupTasks(object) {
      var setupUser,
          internal = {context: {internal: true}};
  
      return utils.checkObject(object, 'setup').then(function (checkedSetupData) {
          setupUser = {
              name: checkedSetupData.setup[0].name,
              email: checkedSetupData.setup[0].email,
              password: checkedSetupData.setup[0].password,
              blogTitle: checkedSetupData.setup[0].blogTitle,
              status: 'active'
          };
  
          return dataProvider.User.findOne({role: 'Owner', status: 'all'});
      }).then(function (ownerUser) {
          if (ownerUser) {
              return dataProvider.User.setup(setupUser, _.extend({id: ownerUser.id}, internal));
          } else {
              return dataProvider.Role.findOne({name: 'Owner'}).then(function (ownerRole) {
                  setupUser.roles = [ownerRole.id];
                  return dataProvider.User.add(setupUser, internal);
              });
          }
      }).then(function (user) {
          var userSettings = [];
  
          // Handles the additional values set by the setup screen.
          if (!_.isEmpty(setupUser.blogTitle)) {
              userSettings.push({key: 'title', value: setupUser.blogTitle});
              userSettings.push({key: 'description', value: 'Learn things :) If this this is coming up, then you have successfully changed the description'});
          }
  
          setupUser = user.toJSON(internal);
          return settings.edit({settings: userSettings}, {context: {user: setupUser.id}});
      }).then(function () {
          return Promise.resolve(setupUser);
      });
  }
  
  /**
   * ## Authentication API Methods
   *
   * **See:** [API Methods](index.js.html#api%20methods)
   */
  authentication = {
  
      /**
       * ## Generate Reset Token
       * generate a reset token for a given email address
       * @param {Object} object
       * @returns {Promise(passwordreset)} message
       */
      generateResetToken: function generateResetToken(object) {
          var expires = Date.now() + globalUtils.ONE_DAY_MS,
              email;
  
          return authentication.isSetup().then(function (result) {
              var setup = result.setup[0].status;
  
              if (!setup) {
                  return Promise.reject(new errors.NoPermissionError('Setup must be completed before making this request.'));
              }
  
              return utils.checkObject(object, 'passwordreset');
          }).then(function (checkedPasswordReset) {
              if (checkedPasswordReset.passwordreset[0].email) {
                  email = checkedPasswordReset.passwordreset[0].email;
              } else {
                  return Promise.reject(new errors.BadRequestError('No email provided.'));
              }
  
              return settings.read({context: {internal: true}, key: 'dbHash'})
              .then(function (response) {
                  var dbHash = response.settings[0].value;
                  return dataProvider.User.generateResetToken(email, expires, dbHash);
              }).then(function (resetToken) {
                  var baseUrl = config.forceAdminSSL ? (config.urlSSL || config.url) : config.url,
                      resetUrl = baseUrl.replace(/\/$/, '') + '/ghost/reset/' + globalUtils.encodeBase64URLsafe(resetToken) + '/';
  
                  return mail.generateContent({data: {resetUrl: resetUrl}, template: 'reset-password'});
              }).then(function (emailContent) {
                  var payload = {
                      mail: [{
                          message: {
                              to: email,
                              subject: 'Reset Password',
                              html: emailContent.html,
                              text: emailContent.text
                          },
                          options: {}
                      }]
                  };
                  return mail.send(payload, {context: {internal: true}});
              }).then(function () {
                  return Promise.resolve({passwordreset: [{message: 'Check your email for further instructions.'}]});
              }).catch(function (error) {
                  return Promise.reject(error);
              });
          });
      },
  
      /**
       * ## Reset Password
       * reset password if a valid token and password (2x) is passed
       * @param {Object} object
       * @returns {Promise(passwordreset)} message
       */
      resetPassword: function resetPassword(object) {
          var resetToken,
              newPassword,
              ne2Password;
  
          return authentication.isSetup().then(function (result) {
              var setup = result.setup[0].status;
  
              if (!setup) {
                  return Promise.reject(new errors.NoPermissionError('Setup must be completed before making this request.'));
              }
  
              return utils.checkObject(object, 'passwordreset');
          }).then(function (checkedPasswordReset) {
              resetToken = checkedPasswordReset.passwordreset[0].token;
              newPassword = checkedPasswordReset.passwordreset[0].newPassword;
              ne2Password = checkedPasswordReset.passwordreset[0].ne2Password;
  
              return settings.read({context: {internal: true}, key: 'dbHash'}).then(function (response) {
                  var dbHash = response.settings[0].value;
                  return dataProvider.User.resetPassword({
                      token: resetToken,
                      newPassword: newPassword,
                      ne2Password: ne2Password,
                      dbHash: dbHash
                  });
              }).then(function () {
                  return Promise.resolve({passwordreset: [{message: 'Password changed successfully.'}]});
              }).catch(function (error) {
                  return Promise.reject(new errors.UnauthorizedError(error.message));
              });
          });
      },
  
      /**
       * ### Accept Invitation
       * @param {User} object the user to create
       * @returns {Promise(User}} Newly created user
       */
      acceptInvitation: function acceptInvitation(object) {
          var resetToken,
              newPassword,
              ne2Password,
              name,
              email;
  
          return authentication.isSetup().then(function (result) {
              var setup = result.setup[0].status;
  
              if (!setup) {
                  return Promise.reject(new errors.NoPermissionError('Setup must be completed before making this request.'));
              }
  
              return utils.checkObject(object, 'invitation');
          }).then(function (checkedInvitation) {
              resetToken = checkedInvitation.invitation[0].token;
              newPassword = checkedInvitation.invitation[0].password;
              ne2Password = checkedInvitation.invitation[0].password;
              email = checkedInvitation.invitation[0].email;
              name = checkedInvitation.invitation[0].name;
  
              return settings.read({context: {internal: true}, key: 'dbHash'}).then(function (response) {
                  var dbHash = response.settings[0].value;
                  return dataProvider.User.resetPassword({
                      token: resetToken,
                      newPassword: newPassword,
                      ne2Password: ne2Password,
                      dbHash: dbHash
                  });
              }).then(function (user) {
                  // Setting the slug to '' has the model regenerate the slug from the user's name
                  return dataProvider.User.edit({name: name, email: email, slug: ''}, {id: user.id});
              }).then(function () {
                  return Promise.resolve({invitation: [{message: 'Invitation accepted.'}]});
              }).catch(function (error) {
                  return Promise.reject(new errors.UnauthorizedError(error.message));
              });
          });
      },
  
      /**
       * ### Check for invitation
       * @param {Object} options
       * @param {string} options.email The email to check for an invitation on
       * @returns {Promise(Invitation}} An invitation status
       */
      isInvitation: function isInvitation(options) {
          return authentication.isSetup().then(function (result) {
              var setup = result.setup[0].status;
  
              if (!setup) {
                  return Promise.reject(new errors.NoPermissionError('Setup must be completed before making this request.'));
              }
  
              if (options.email) {
                  return dataProvider.User.findOne({email: options.email, status: 'invited'}).then(function (response) {
                      if (response) {
                          return {invitation: [{valid: true}]};
                      } else {
                          return {invitation: [{valid: false}]};
                      }
                  });
              } else {
                  return Promise.reject(new errors.BadRequestError('The server did not receive a valid email'));
              }
          });
      },
  
      isSetup: function isSetup() {
          return dataProvider.User.query(function (qb) {
              qb.whereIn('status', ['active', 'warn-1', 'warn-2', 'warn-3', 'warn-4', 'locked']);
          }).fetch().then(function (users) {
              if (users) {
                  return Promise.resolve({setup: [{status: true}]});
              } else {
                  return Promise.resolve({setup: [{status: false}]});
              }
          });
      },
  
      setup: function setup(object) {
          var setupUser;
  
          return authentication.isSetup().then(function (result) {
              var setup = result.setup[0].status;
  
              if (setup) {
                  return Promise.reject(new errors.NoPermissionError('Setup has already been completed.'));
              }
  
              return setupTasks(object);
          }).then(function (result) {
              setupUser = result;
  
              var data = {
                  ownerEmail: setupUser.email
              };
  
              return mail.generateContent({data: data, template: 'welcome'});
          }).then(function (emailContent) {
              var message = {
                      to: setupUser.email,
                      subject: 'Your New Ghost Blog',
                      html: emailContent.html,
                      text: emailContent.text
                  },
                  payload = {
                      mail: [{
                          message: message,
                          options: {}
                      }]
                  };
  
              mail.send(payload, {context: {internal: true}}).catch(function (error) {
                  errors.logError(
                      error.message,
                      'Unable to send welcome email, your blog will continue to function.',
                      'Please see http://support.ghost.org/mail/ for instructions on configuring email.'
                  );
              });
          }).then(function () {
              return Promise.resolve({users: [setupUser]});
          });
      },
  
      updateSetup: function updateSetup(object, options) {
          if (!options.context || !options.context.user) {
              return Promise.reject(new errors.NoPermissionError('You are not logged in.'));
          }
  
          return dataProvider.User.findOne({role: 'Owner', status: 'all'}).then(function (result) {
              var user = result.toJSON();
  
              if (user.id !== options.context.user) {
                  return Promise.reject(new errors.NoPermissionError('You are not the blog owner.'));
              }
  
              return setupTasks(object);
          }).then(function (result) {
              return Promise.resolve({users: [result]});
          });
      },
  
      revoke: function (object) {
          var token;
  
          if (object.token_type_hint && object.token_type_hint === 'access_token') {
              token = dataProvider.Accesstoken;
          } else if (object.token_type_hint && object.token_type_hint === 'refresh_token') {
              token = dataProvider.Refreshtoken;
          } else {
              return errors.BadRequestError('Invalid token_type_hint given.');
          }
  
          return token.destroyByToken({token: object.token}).then(function () {
              return Promise.resolve({token: object.token});
          }, function () {
              // On error we still want a 200. See https://tools.ietf.org/html/rfc7009#page-5
              return Promise.resolve({token: object.token, error: 'Invalid token provided'});
          });
      }
  };
  
  module.exports = authentication;
  