// # Users API
// RESTful API for the User resource
var Promise         = require('bluebird'),
    _               = require('lodash'),
    dataProvider    = require('../models'),
    settings        = require('./settings'),
    canThis         = require('../permissions').canThis,
    errors          = require('../errors'),
    utils           = require('./utils'),
    globalUtils     = require('../utils'),
    config          = require('../config'),
    mail            = require('./mail'),

    docName         = 'users',
    // TODO: implement created_by, updated_by
    allowedIncludes = ['permissions', 'roles', 'roles.permissions'],
    users,
    sendInviteEmail;

// ## Helpers
function prepareInclude(include) {
    include = include || '';
    include = _.intersection(include.split(','), allowedIncludes);
    return include;
}

sendInviteEmail = function sendInviteEmail(user) {
    var emailData;

    return Promise.join(
        users.read({id: user.created_by}),
        settings.read({key: 'title'}),
        settings.read({context: {internal: true}, key: 'dbHash'})
    ).then(function (values) {
        var invitedBy = values[0].users[0],
            blogTitle = values[1].settings[0].value,
            expires = Date.now() + (14 * globalUtils.ONE_DAY_MS),
            dbHash = values[2].settings[0].value;

        emailData = {
            blogName: blogTitle,
            invitedByName: invitedBy.name,
            invitedByEmail: invitedBy.email
        };

        return dataProvider.User.generateResetToken(user.email, expires, dbHash);
    }).then(function (resetToken) {
        var baseUrl = config.forceAdminSSL ? (config.urlSSL || config.url) : config.url;

        emailData.resetLink = baseUrl.replace(/\/$/, '') + '/ghost/signup/' + globalUtils.encodeBase64URLsafe(resetToken) + '/';

        return mail.generateContent({data: emailData, template: 'invite-user'});
    }).then(function (emailContent) {
        var payload = {
            mail: [{
                message: {
                    to: user.email,
                    subject: emailData.invitedByName + ' has invited you to join ' + emailData.blogName,
                    html: emailContent.html,
                    text: emailContent.text
                },
                options: {}
            }]
        };

        return mail.send(payload, {context: {internal: true}});
    });
};
/**
 * ## Posts API Methods
 *
 * **See:** [API Methods](index.js.html#api%20methods)
 */
users = {

    /**
     * ## Browse
     * Fetch all users
     * @param {{context}} options (optional)
     * @returns {Promise(Users)} Users Collection
     */
    browse: function browse(options) {
        options = options || {};
        return canThis(options.context).browse.user().then(function () {
            if (options.include) {
                options.include = prepareInclude(options.include);
            }
            return dataProvider.User.findPage(options);
        }).catch(function (error) {
            return errors.handleAPIError(error, 'You do not have permission to browse users.');
        });
    },

    /**
     * ### Read
     * @param {{id, context}} options
     * @returns {Promise(User)} User
     */
    read: function read(options) {
        var attrs = ['id', 'slug', 'email', 'status'],
            data = _.pick(options, attrs);

        options = _.omit(options, attrs);

        if (options.include) {
            options.include = prepareInclude(options.include);
        }

        if (data.id === 'me' && options.context && options.context.user) {
            data.id = options.context.user;
        }

        return dataProvider.User.findOne(data, options).then(function (result) {
            if (result) {
                return {users: [result.toJSON()]};
            }

            return Promise.reject(new errors.NotFoundError('User not found.'));
        });
    },

    /**
     * ### Edit
     * @param {User} object the user details to edit
     * @param {{id, context}} options
     * @returns {Promise(User)}
     */
    edit: function edit(object, options) {
        var editOperation;
        if (options.id === 'me' && options.context && options.context.user) {
            options.id = options.context.user;
        }

        if (options.include) {
            options.include = prepareInclude(options.include);
        }

        return utils.checkObject(object, docName).then(function (data) {
            // Edit operation
            editOperation = function () {
                return dataProvider.User.edit(data.users[0], options)
                    .then(function (result) {
                        if (result) {
                            return {users: [result.toJSON()]};
                        }

                        return Promise.reject(new errors.NotFoundError('User not found.'));
                    });
            };

            // Check permissions
            return canThis(options.context).edit.user(options.id).then(function () {
                // if roles aren't in the payload, proceed with the edit
                if (!(data.users[0].roles && data.users[0].roles[0])) {
                    return editOperation();
                }

                var role = data.users[0].roles[0],
                    roleId = parseInt(role.id || role, 10),
                    editedUserId = parseInt(options.id, 10);

                return dataProvider.User.findOne(
                    {id: options.context.user, status: 'all'}, {include: ['roles']}
                ).then(function (contextUser) {
                    var contextRoleId = contextUser.related('roles').toJSON()[0].id;

                    if (roleId !== contextRoleId && editedUserId === contextUser.id) {
                        return Promise.reject(new errors.NoPermissionError('You cannot change your own role.'));
                    }

                    return dataProvider.User.findOne({role: 'Owner'}).then(function (owner) {
                        if (contextUser.id !== owner.id) {
                            if (editedUserId === owner.id) {
                                if (owner.related('roles').at(0).id !== roleId) {
                                    return Promise.reject(new errors.NoPermissionError('Cannot change Owner\'s role.'));
                                }
                            } else if (roleId !== contextRoleId) {
                                return canThis(options.context).assign.role(role).then(function () {
                                    return editOperation();
                                });
                            }
                        }

                        return editOperation();
                    });
                });
            });
        }).catch(function (error) {
            return errors.handleAPIError(error, 'You do not have permission to edit this user');
        });
    },

    /**
     * ### Add user
     * The newly added user is invited to join the blog via email.
     * @param {User} object the user to create
     * @returns {Promise(User}} Newly created user
     */
    add: function add(object, options) {
        var addOperation,
            newUser,
            user;

        if (options.include) {
            options.include = prepareInclude(options.include);
        }

        return utils.checkObject(object, docName).then(function (data) {
            newUser = data.users[0];

            addOperation = function () {
                if (newUser.email) {
                    newUser.name = object.users[0].email.substring(0, newUser.email.indexOf('@'));
                    newUser.password = globalUtils.uid(50);
                    newUser.status = 'invited';
                } else {
                    return Promise.reject(new errors.BadRequestError('No email provided.'));
                }

                return dataProvider.User.getByEmail(
                    newUser.email
                ).then(function (foundUser) {
                    if (!foundUser) {
                        return dataProvider.User.add(newUser, options);
                    } else {
                        // only invitations for already invited users are resent
                        if (foundUser.get('status') === 'invited' || foundUser.get('status') === 'invited-pending') {
                            return foundUser;
                        } else {
                            return Promise.reject(new errors.BadRequestError('User is already registered.'));
                        }
                    }
                }).then(function (invitedUser) {
                    user = invitedUser.toJSON();
                    return sendInviteEmail(user);
                }).then(function () {
                    // If status was invited-pending and sending the invitation succeeded, set status to invited.
                    if (user.status === 'invited-pending') {
                        return dataProvider.User.edit(
                            {status: 'invited'}, _.extend({}, options, {id: user.id})
                        ).then(function (editedUser) {
                            user = editedUser.toJSON();
                        });
                    }
                }).then(function () {
                    return Promise.resolve({users: [user]});
                }).catch(function (error) {
                    if (error && error.type === 'EmailError') {
                        error.message = 'Error sending email: ' + error.message + ' Please check your email settings and resend the invitation.';
                        errors.logWarn(error.message);

                        // If sending the invitation failed, set status to invited-pending
                        return dataProvider.User.edit({status: 'invited-pending'}, {id: user.id}).then(function (user) {
                            return dataProvider.User.findOne({id: user.id, status: 'all'}, options).then(function (user) {
                                return {users: [user]};
                            });
                        });
                    }
                    return Promise.reject(error);
                });
            };

            // Check permissions
            return canThis(options.context).add.user(object).then(function () {
                if (newUser.roles && newUser.roles[0]) {
                    var roleId = parseInt(newUser.roles[0].id || newUser.roles[0], 10);

                    // Make sure user is allowed to add a user with this role
                    return dataProvider.Role.findOne({id: roleId}).then(function (role) {
                        if (role.get('name') === 'Owner') {
                            return Promise.reject(new errors.NoPermissionError('Not allowed to create an owner user.'));
                        }

                        return canThis(options.context).assign.role(role);
                    }).then(function () {
                        return addOperation();
                    });
                }

                return addOperation();
            });
        }).catch(function (error) {
            return errors.handleAPIError(error, 'You do not have permission to add this user');
        });
    },

    /**
     * ### Destroy
     * @param {{id, context}} options
     * @returns {Promise(User)}
     */
    destroy: function destroy(options) {
        return canThis(options.context).destroy.user(options.id).then(function () {
            return users.read(_.merge(options, {status: 'all'})).then(function (result) {
                return dataProvider.Base.transaction(function (t) {
                    options.transacting = t;

                    Promise.all([
                        dataProvider.Accesstoken.destroyByUser(options),
                        dataProvider.Refreshtoken.destroyByUser(options),
                        dataProvider.Post.destroyByAuthor(options)
                    ]).then(function () {
                        return dataProvider.User.destroy(options);
                    }).then(function () {
                        t.commit();
                    }).catch(function (error) {
                        t.rollback(error);
                    });
                }).then(function () {
                    return result;
                }, function (error) {
                    return Promise.reject(new errors.InternalServerError(error));
                });
            }, function (error) {
                return errors.handleAPIError(error);
            });
        }).catch(function (error) {
            return errors.handleAPIError(error, 'You do not have permission to destroy this user');
        });
    },

    /**
     * ### Change Password
     * @param {password} object
     * @param {{context}} options
     * @returns {Promise(password}} success message
     */
    changePassword: function changePassword(object, options) {
        var oldPassword,
            newPassword,
            ne2Password,
            userId;

        return utils.checkObject(object, 'password').then(function (checkedPasswordReset) {
            oldPassword = checkedPasswordReset.password[0].oldPassword;
            newPassword = checkedPasswordReset.password[0].newPassword;
            ne2Password = checkedPasswordReset.password[0].ne2Password;
            userId = parseInt(checkedPasswordReset.password[0].user_id);
        }).then(function () {
            return canThis(options.context).edit.user(userId);
        }).then(function () {
            return dataProvider.User.changePassword(oldPassword, newPassword, ne2Password, userId, options);
        }).then(function () {
            return Promise.resolve({password: [{message: 'Password changed successfully.'}]});
        }).catch(function (error) {
            // return Promise.reject(new errors.ValidationError(error.message));
            return errors.handleAPIError(error, 'You do not have permission to change the password for this user');
        });
    },

    /**
     *
     */
    transferOwnership: function transferOwnership(object, options) {
        return dataProvider.Role.findOne({name: 'Owner'}).then(function (ownerRole) {
            return canThis(options.context).assign.role(ownerRole);
        }).then(function () {
            return utils.checkObject(object, 'owner').then(function (checkedOwnerTransfer) {
                return dataProvider.User.transferOwnership(checkedOwnerTransfer.owner[0], options).then(function (updatedUsers) {
                    return Promise.resolve({users: updatedUsers});
                }).catch(function (error) {
                    return Promise.reject(new errors.ValidationError(error.message));
                });
            });
        }).catch(function (error) {
            return errors.handleAPIError(error);
        });
    }
};

module.exports = users;