/* eslint-disable object-shorthand */
var _ = require('lodash'),
    url = require('./url'),
    rnd = Math.random;

module.exports = {
    // @todo: Add support for a `json` type once it becomes available
    typeMap: {
        string: 'string',
        boolean: 'boolean',
        number: 'number'
    },

    /**
     * Returns unique GUID on every call as per pseudo-number RFC4122 standards.
     *
     * @type {function}
     * @returns {string}
     */
    uid: function () {
        var n,
            r,
            E = '',
            H = '-'; // r = result , n = numeric variable for positional checks

        // if "n" is not 9 or 14 or 19 or 24 return a random number or 4
        // if "n" is not 15 generate a random number from 0 to 15
        // `(n ^ 20 ? 16 : 4)` := unless "n" is 20, in which case a random number from 8 to 11 otherwise 4
        //
        // in other cases (if "n" is 9,14,19,24) insert "-"
        // eslint-disable-next-line curly
        for (r = n = E; n++ < 36; r += n * 51 & 52 ? (n ^ 15 ? 8 ^ rnd() * (n ^ 20 ? 16 : 4) : 4).toString(16) : H);

        return r;
    },

    urlparse: function (u) {
        return url.parse(u);
    },

    urlunparse: function (urlObj) {
        return url.unparse(urlObj);
    },

    /**
     * A generic utility method to sanitize variable transformations across collection formats.
     *
     * @param {Object} entity - A generic object that could contain variable data.
     * @param {?Object} options - The set of options for variable handling.
     * @param {?Object} options.fallback - The fallback values to be used if no variables are present.
     * @param {?Boolean} options.noDefaults - When set to true, id will be retained.
     * @param {?Boolean} options.retainEmptyValues - When set to true, empty property values will be set to ''
     * @param {?Boolean} options.retainIds - When set to true, ids are left as is.
     * instead of being deleted.
     * @param {Object} [modifiers] - A set of behavioral modifiers for variable handling.
     * @param {Boolean} [modifiers.isV1=false] - When set to true, looks for the pathVariableData property as well.
     * @returns {Object[]} - The set of sanitized entity level variables.
     */
    handleVars: function (entity, options, modifiers) {
        !options && (options = {});

        var self = this,
            retainIds = options.retainIds,
            noDefaults = options.noDefaults,
            isV1 = modifiers && modifiers.isV1,
            retainEmpty = options.retainEmptyValues,
            source = entity && (entity.variables || entity.variable || (isV1 && entity.pathVariableData)),
            fallback = options.fallback && options.fallback.values,
            result = _.map(source || fallback, function (item) {
                var result = {};

                // add id only when retainIds is set
                if (retainIds) {
                    // retain id only when `id` field is present
                    if (_.has(item, 'id')) {
                        result.id = item.id;
                    }
                    // or, create a new id if noDefaults is false
                    else if (!noDefaults) {
                        result.id = self.uid();
                    }
                }

                result.key = item.key || item.id;
                result.value = item.value;

                item.type && (result.type = item.type === 'text' ? 'string' : item.type);
                item.disabled && (result.disabled = true);

                if (item.description) { result.description = item.description; }
                else if (retainEmpty) { result.description = null; }

                return result;
            });

        if (result.length) { return result; }
    },

    /**
     * Performs auth cleansing common to all sorts of auth transformations.
     *
     * @param {Object} entity - The wrapped auth entity to be cleaned.
     * @param {?Object} options - The set of options for the current auth cleansing operation.
     * @param {?Boolean} [options.excludeNoauth=false] - When set to true, noauth is set to null.
     * @returns {Object|*} - The processed auth data.
     */
    cleanAuth: function (entity, options) {
        !options && (options = {});

        var auth = entity && entity.auth;

        if (auth === null) { return null; } // eslint-disable-line security/detect-possible-timing-attacks
        if (!(auth && auth.type)) { return; }
        if (auth.type === 'noauth') {
            return options.excludeNoauth ? null : { type: 'noauth' };
        }

        return auth;
    },

    cleanEmptyValue: function (entity, property, retainEmpty) {
        if (_.has(entity, property) && !entity[property]) {
            retainEmpty ? (entity[property] = null) : (delete entity[property]);
        }

        return entity;
    },

    /**
     * Transforms an array of auth params to their object equivalent.
     *
     * @param {Object} entity - The wrapper object for the array of auth params.
     * @param {?Object} options - The set of options for the current auth cleansing operation.
     * @param {?Boolean} [options.excludeNoauth=false] - When set to true, noauth is set to null.
     * @returns {*}
     */
    authArrayToMap: function (entity, options) {
        var type,
            result,
            self = this,
            auth = self.cleanAuth(entity, options);

        if (!auth) { return auth; }

        result = { type: (type = auth.type) };

        if (type !== 'noauth') {
            result[type] = _.transform(auth[type], function (result, param) {
                result[param.key] = param.value;
            }, {});
        }

        return result;
    },

    /**
     * Transforms an object of auth params to their array equivalent.
     *
     * @param {Object} entity - The wrapper object for the array of auth params.
     * @param {?Object} options - The set of options for the current auth cleansing operation.
     * @param {?Boolean} [options.excludeNoauth=false] - When set to true, noauth is set to null.
     * @returns {*}
     */
    authMapToArray: function (entity, options) {
        var type,
            params,
            result,
            self = this,
            auth = self.cleanAuth(entity, options);

        if (!auth) { return auth; }

        result = { type: (type = auth.type) };

        if (type !== 'noauth') {
            // @todo: Handle all non _ prefixed properties, ala request bodies
            params = _.map(auth[type], function (value, key) {
                return {
                    key: key,
                    value: value,
                    type: self.typeMap[typeof value] || 'any'
                };
            });

            params.length && (result[type] = params);
        }

        return result;
    },

    /**
     * Sanitizes a collection SDK compliant auth list.
     *
     * @param {Object} entity - The wrapper entity for the auth manifest.
     * @param {?Object} options - The set of options for the current auth cleansing operation.
     * @param {?Boolean} [options.excludeNoauth=false] - When set to true, noauth is set to null.
     * @returns {Object[]} - An array of raw collection SDK compliant auth parameters.
     */
    sanitizeAuthArray: function (entity, options) {
        var type,
            result,
            self = this,
            auth = self.cleanAuth(entity, options);

        if (!auth) { return auth; }

        result = { type: (type = auth.type) };

        if (type !== 'noauth') {
            result[type] = _.map(auth[type], function (param) {
                return {
                    key: param.key,
                    value: param.value,
                    type: (param.type === 'text' ? 'string' : param.type) || self.typeMap[typeof param.value] || 'any'
                };
            });
        }

        return result;
    },

    /**
     * A helper function to determine if the provided v1 entity has legacy properties.
     *
     * @private
     * @param {Object} entityV1 - The v1 entity to be checked for the presence of legacy properties.
     * @param {String} type - The type of property to be adjudged against.
     * @returns {Boolean|*} - A flag to indicate the legacy property status of the passed v1 entity.
     */
    notLegacy: function (entityV1, type) {
        if (!entityV1) { return; }

        switch (type) {
            case 'event':
                return !(entityV1.tests || entityV1.preRequestScript);
            case 'auth':
                return _.has(entityV1, 'auth') && !(_.has(entityV1, 'currentHelper') || entityV1.helperAttributes);
            default:
                return true;
        }
    },

    authMappersFromLegacy: {
        apikeyAuth: function (oldParams) {
            return oldParams && {
                key: oldParams.key,
                value: oldParams.value,
                in: oldParams.in
            };
        },
        basicAuth: function (oldParams) {
            return oldParams && {
                username: oldParams.username,
                password: oldParams.password,
                saveHelperData: oldParams.saveToRequest,
                showPassword: false
            };
        },
        bearerAuth: function (oldParams) {
            return oldParams && {
                token: oldParams.token
            };
        },
        digestAuth: function (oldParams) {
            return oldParams && {
                algorithm: oldParams.algorithm,
                username: oldParams.username,
                realm: oldParams.realm,
                password: oldParams.password,
                nonce: oldParams.nonce,
                nonceCount: oldParams.nonceCount,
                clientNonce: oldParams.clientNonce,
                opaque: oldParams.opaque,
                qop: oldParams.qop,
                disableRetryRequest: oldParams.disableRetryRequest
            };
        },
        oAuth1: function (oldParams) {
            return oldParams && {
                consumerKey: oldParams.consumerKey,
                consumerSecret: oldParams.consumerSecret,
                token: oldParams.token,
                tokenSecret: oldParams.tokenSecret,
                signatureMethod: oldParams.signatureMethod,
                timestamp: oldParams.timestamp,
                nonce: oldParams.nonce,
                version: oldParams.version,
                realm: oldParams.realm,
                addParamsToHeader: oldParams.header,
                autoAddParam: oldParams.auto,
                addEmptyParamsToSign: oldParams.includeEmpty
            };
        },
        hawkAuth: function (oldParams) {
            return oldParams && {
                authId: oldParams.hawk_id,
                authKey: oldParams.hawk_key,
                algorithm: oldParams.algorithm,
                user: oldParams.user,
                saveHelperData: oldParams.saveToRequest,
                nonce: oldParams.nonce,
                extraData: oldParams.ext,
                appId: oldParams.app,
                delegation: oldParams.dlg,
                timestamp: oldParams.timestamp
            };
        },
        ntlmAuth: function (oldParams) {
            return oldParams && {
                username: oldParams.username,
                password: oldParams.password,
                domain: oldParams.domain,
                workstation: oldParams.workstation,
                disableRetryRequest: oldParams.disableRetryRequest
            };
        },
        oAuth2: function (oldParams) {
            return oldParams && {
                accessToken: oldParams.accessToken,
                addTokenTo: oldParams.addTokenTo,
                callBackUrl: oldParams.callBackUrl,
                authUrl: oldParams.authUrl,
                accessTokenUrl: oldParams.accessTokenUrl,
                clientId: oldParams.clientId,
                clientSecret: oldParams.clientSecret,
                clientAuth: oldParams.clientAuth,
                grantType: oldParams.grantType,
                scope: oldParams.scope,
                username: oldParams.username,
                password: oldParams.password,
                tokenType: oldParams.tokenType,
                redirectUri: oldParams.redirectUri,
                refreshToken: oldParams.refreshToken
            };
        },
        // Only exists for consistency
        awsSigV4: function (oldParams) {
            return oldParams;
        }
    },
    authMappersFromCurrent: {
        apikeyAuth: function (newParams) {
            return newParams && {
                id: 'apikey',
                key: newParams.key,
                value: newParams.value,
                in: newParams.in
            };
        },
        basicAuth: function (newParams) {
            return newParams && {
                id: 'basic',
                username: newParams.username,
                password: newParams.password,
                saveToRequest: newParams.saveHelperData
            };
        },
        bearerAuth: function (newParams) {
            return newParams && {
                id: 'bearer',
                token: newParams.token
            };
        },
        digestAuth: function (newParams) {
            return newParams && {
                id: 'digest',
                algorithm: newParams.algorithm,
                username: newParams.username,
                realm: newParams.realm,
                password: newParams.password,
                nonce: newParams.nonce,
                nonceCount: newParams.nonceCount,
                clientNonce: newParams.clientNonce,
                opaque: newParams.opaque,
                qop: newParams.qop,
                disableRetryRequest: newParams.disableRetryRequest
            };
        },
        oAuth1: function (newParams) {
            return newParams && {
                id: 'oAuth1',
                consumerKey: newParams.consumerKey,
                consumerSecret: newParams.consumerSecret,
                token: newParams.token,
                tokenSecret: newParams.tokenSecret,
                signatureMethod: newParams.signatureMethod,
                timestamp: newParams.timeStamp || newParams.timestamp,
                nonce: newParams.nonce,
                version: newParams.version,
                realm: newParams.realm,
                header: newParams.addParamsToHeader,
                auto: newParams.autoAddParam,
                includeEmpty: newParams.addEmptyParamsToSign
            };
        },
        hawkAuth: function (newParams) {
            return newParams && {
                id: 'hawk',
                hawk_id: newParams.authId,
                hawk_key: newParams.authKey,
                algorithm: newParams.algorithm,
                user: newParams.user,
                saveToRequest: newParams.saveHelperData,
                nonce: newParams.nonce,
                ext: newParams.extraData,
                app: newParams.appId,
                dlg: newParams.delegation,
                timestamp: newParams.timestamp
            };
        },
        ntlmAuth: function (newParams) {
            return newParams && {
                id: 'ntlm',
                username: newParams.username,
                password: newParams.password,
                domain: newParams.domain,
                workstation: newParams.workstation,
                disableRetryRequest: newParams.disableRetryRequest
            };
        },
        oAuth2: function (newParams) {
            return newParams && {
                id: 'oAuth2',
                accessToken: newParams.accessToken,
                addTokenTo: newParams.addTokenTo,
                callBackUrl: newParams.callBackUrl,
                authUrl: newParams.authUrl,
                accessTokenUrl: newParams.accessTokenUrl,
                clientId: newParams.clientId,
                clientSecret: newParams.clientSecret,
                clientAuth: newParams.clientAuth,
                grantType: newParams.grantType,
                scope: newParams.scope,
                username: newParams.username,
                password: newParams.password,
                tokenType: newParams.tokenType,
                redirectUri: newParams.redirectUri,
                refreshToken: newParams.refreshToken
            };
        },
        // Only exists for consistency
        awsSigV4: function (newParams) {
            return newParams;
        }
    },

    /**
     * Validate protocolProfileBehavior property's value.
     *
     * @param {Object} source - A generic object that could contain the protocolProfileBehavior property.
     * @param {?Object} destination - The destination object that needs the addition of protocolProfileBehavior.
     * @returns {Boolean} - A Boolean value to decide whether to include the property or not.
     */
    addProtocolProfileBehavior: function (source, destination) {
        var behavior = source && source.protocolProfileBehavior;

        // make sure it's a non-empty plain object
        if (!(_.isPlainObject(behavior) && !_.isEmpty(behavior))) { return false; }

        destination && (destination.protocolProfileBehavior = behavior);

        return true;
    }
};
