const _ = require('lodash');
const logger = require('../lib/logger');

const VALID_REQUEST_TYPES = ['get', 'post', 'put', 'patch', 'del'];

const VALID_HOST_COMPONENT_PATTERN = /^[^\?=#<>]*$/;
const VALID_PATH_COMPONENT_PATTERN = /^[^\?\/=#<>]*$/;

const REQUEST_METHOD_MAP = {
    delete: 'del',
};

const FORMAT_MAP = {
    number: /^\d+$/,
    domain: /^[^\?\/=:@&+$]*$/,
    code: /^[a-z\d]+$/,
};

function sanitizeUrlComponent(req, s, index) {
    if (s !== null && typeof s === 'object') {
        const typeExpression = s.type ? FORMAT_MAP[s.type] : null;

        if (typeExpression && !typeExpression.test(s.value)) {
            logger.warn(`The request URL component '${s.value}' does not match the type '${s.type}'`, req);

            return null;
        }

        const formatExpression = s.format ? new RegExp(s.format) : null;

        if (formatExpression && !formatExpression.test(s.value)) {
            logger.warn(`The request URL component '${s.value}' does not match the format '${s.format}'`, req);

            return null;
        }

        s = s.value;
    }

    if (typeof s === 'string') {
        const pattern = index === 0 ? VALID_HOST_COMPONENT_PATTERN : VALID_PATH_COMPONENT_PATTERN;

        if (!pattern.test(s)) {
            logger.warn(`The request URL component '${s}' is potentially unsafe`, req);

            return null;
        }
    }

    return s;
}

function sanitizeRequest(req) {
    const sanitizedOptions = {};

    sanitizedOptions.requestMethodName = REQUEST_METHOD_MAP[req.method] || req.method;

    if (VALID_REQUEST_TYPES.indexOf(sanitizedOptions.requestMethodName) === -1) {
        logger.warn(`Invalid request method: '${req.method}'`, req, { url: req.url, method: req.method });
        sanitizedOptions.unsafe = true;
    }

    if (Array.isArray(req.url)) {
        const originalUrl = req.url.filter(Boolean)
            .map(s => {
                if (typeof s === 'string') {
                    return s;
                }

                return s ? s.value : '';
            }).join('/');
        const sanitizedUrl = req.url.filter(Boolean)
            .map(sanitizeUrlComponent.bind(null, req)).join('/');

        req.url = originalUrl;
        sanitizedOptions.unsafe = sanitizedUrl !== originalUrl;
    }

    if (!req.url.endsWith('/') && !req.noTrailingSlash) {
        req.url += '/';
    }

    return _.extend(req, sanitizedOptions);
}

module.exports = sanitizeRequest;
