const _ = require('lodash');

const validator = require('@tryghost/validator');

const tpl = require('@tryghost/tpl');
const settingsCache = require('../../shared/settings-cache');
const urlUtils = require('../../shared/url-utils').default;

const messages = {
    passwordDoesNotComplyLength: 'Your password must be at least {minLength} characters long.',
    passwordDoesNotComplySecurity: 'Sorry, you cannot use an insecure password.'
};

/**
 * Counts repeated characters in a string. When 50% or more characters are the same,
 * we return false and therefore invalidate the string.
 * @param {string} stringToTest The password string to check.
 * @return {boolean}
 */
function characterOccurance(stringToTest) {
    const chars = {};
    let allowedOccurancy;
    let valid = true;

    stringToTest = _.toString(stringToTest);
    allowedOccurancy = stringToTest.length / 2;

    // Loop through string and accumulate character counts
    _.each(stringToTest, function (char) {
        if (!chars[char]) {
            chars[char] = 1;
        } else {
            chars[char] += 1;
        }
    });

    // check if any of the accumulated chars exceed the allowed occurancy
    // of 50% of the words' length.
    _.forIn(chars, function (charCount) {
        if (charCount >= allowedOccurancy) {
            valid = false;
        }
    });

    return valid;
}

/**
 * Validation against simple password rules
 * Returns false when validation fails and true for a valid password
 * @param {string} password The password string to check.
 * @param {string} email The users email address to validate agains password.
 * @param {string} [blogTitle] Optional blogTitle value, when blog title is not set yet, e. g. in setup process.
 * @return {Object} example for returned validation Object:
 * invalid password: `validationResult: {isValid: false, message: 'Sorry, you cannot use an insecure password.'}`
 * valid password: `validationResult: {isValid: true}`
 */
function validatePassword(password, email, blogTitle) {
    const validationResult = {isValid: true};
    const disallowedPasswords = ['password', 'ghost', 'passw0rd'];
    let blogUrl = urlUtils.urlFor('home', true);

    const badPasswords = [
        '1234567890',
        'qwertyuiop',
        'qwertzuiop',
        'asdfghjkl;',
        'abcdefghij',
        '0987654321',
        '1q2w3e4r5t',
        '12345asdfg'
    ];

    blogTitle = blogTitle ? blogTitle : settingsCache.get('title');
    blogUrl = blogUrl.replace(/^http(s?):\/\//, '');

    // password must be longer than 10 characters
    if (!validator.isLength(password, 10)) {
        validationResult.isValid = false;
        validationResult.message = tpl(messages.passwordDoesNotComplyLength, {minLength: 10});

        return validationResult;
    }

    // dissallow password from badPasswords list (e. g. '1234567890')
    _.each(badPasswords, function (badPassword) {
        if (badPassword === password) {
            validationResult.isValid = false;
        }
    });

    // password must not match with users' email
    if (email && email.toLowerCase() === password.toLowerCase()) {
        validationResult.isValid = false;
    }

    // password must not contain the words 'ghost', 'password', or 'passw0rd'
    _.each(disallowedPasswords, function (disallowedPassword) {
        if (password.toLowerCase().indexOf(disallowedPassword) >= 0) {
            validationResult.isValid = false;
        }
    });

    // password must not match with blog title
    if (blogTitle && blogTitle.toLowerCase() === password.toLowerCase()) {
        validationResult.isValid = false;
    }

    // password must not match with blog URL (without protocol, with or without trailing slash)
    if (blogUrl && (blogUrl.toLowerCase() === password.toLowerCase() || blogUrl.toLowerCase().replace(/\/$/, '') === password.toLowerCase())) {
        validationResult.isValid = false;
    }

    // dissallow passwords where 50% or more of characters are the same
    if (!characterOccurance(password)) {
        validationResult.isValid = false;
    }

    // Generic error message for the rules where no dedicated error massage is set
    if (!validationResult.isValid && !validationResult.message) {
        validationResult.message = tpl(messages.passwordDoesNotComplySecurity);
    }

    return validationResult;
}

module.exports = validatePassword;
