const _ = require('lodash');
const fetch = require('node-fetch');
const querystring = require('querystring');
const log = require('../util/common/log');

const DEFAULT_API = 'directory_admin';
const AUX_PROPS = [ '_api', '_method', '_path', '_headers', '_body', '_return', '_silent' ];

module.exports = (req, res, next) => {
    let requestUrl = `${getAPIRoot(req)}${getValue(req, '_path') || req.path}`;
    let options = {
        method: getMethod(req),
        headers: req.context.headers
    };

    const submittedApiKey = getValue(req, '_api');
    const tvmTicket = submittedApiKey && _.get(req.context, ['tvm_tickets', submittedApiKey]);

    if (tvmTicket) {
        options.headers = {
            'X-Ya-Service-Ticket': tvmTicket
        };
    }

    const headers = parseValues(getValue(req, '_headers'));

    if (headers) {
        Object.assign(options.headers, headers);
    }

    if (submittedApiKey === 'dns') {
        delete options.headers['X-Ya-User-Ticket'];
    }

    let body = getValue(req, '_body');

    if (body === undefined && req.body) {
        body = JSON.stringify(parseValues(req.body));
        options.headers['Content-Type'] = 'application/json';
    }

    if (body !== undefined) {
        options.body = body;
    }

    if (!_.isEmpty(req.query))
        requestUrl += '?' + querystring.stringify(parseValues(req.query));

    if (req.baseUrl === '/forms') {
        let nextUrl = getValue(req, '_return') || req.header('Referer');
        let meta = {};

        return log(fetch)(requestUrl, options)
            .then(res => {
                meta = _.pick(res, [ 'ok', 'status', 'statusText' ]);

                if (res.status === 204) { // No content
                    return Promise.resolve(null);
                }

                return res.json();
            })
            .then(data => {
                data = Object.assign(data || {}, { meta });

                if (nextUrl) {
                    if (!getValue(req, '_silent')) {
                        nextUrl = addStatusParameters(req, nextUrl, data);
                    }

                    return res.redirect(302, nextUrl);
                }

                next();
            })
            .catch(next);
    }

    return log(fetch)(requestUrl, options)
        .then(res => res.json())
        .then(data => {
            res.setHeader('Content-Type', 'application/json');
            res.send(JSON.stringify(data));
        })
        .catch(next);
};

function getAPIKey(req) {
    return getValue(req, '_api') || DEFAULT_API;
}

function getAPIRoot(req) {
    return req.context.config.api[getAPIKey(req)];
}

function getMethod(req) {
    return getValue(req, '_method') || req.method || 'GET';
}

function getValue(req, key) {
    return _.get(req.body, key) || _.get(req.query, key);
}

const VALUE_CAST = {
    'number': Number,
    'boolean': Boolean,
    'date': String,
    'json': JSON.parse,
    'object': JSON.parse
};

// парсинг текстовых пар name-value из html-форм
//  input: { "x<number>": "1", "y": "abc", "z<boolean>": "true" }
// output: { "x": 1, "y": "abc", "z": true }

function parseValues(data) {
    data = _.omit(data, AUX_PROPS);

    Object.keys(data).forEach(key => {
        if (data[key] && typeof data[key] === 'object')
            return (data[key] = parseValues(data[key]));

        // data['prop<type>']
        // 'prop<type>' > [ 'prop<type>', 'prop', 'type' ]
        let components = key.match(/^([^<]+)<([^>]+)>$/);

        if (components) {
            let value = data[key];

            if (VALUE_CAST[components[2]])
                value = value === '' ? null : VALUE_CAST[components[2]](value);

            data[components[1]] = value;
            delete data[key];
        }
    });

    return data;
}

function addStatusParameters(req, url, data) {
    let urlComponents = url.match(/^([^\?#]+)(\?[^#]+)?(#.+)?$/);
    let method = getMethod(req).toLowerCase();

    let originalQueryData = urlComponents[2] ? querystring.parse(urlComponents[2].substring(1)) : {};

    let statusData = {
        request: `${method} <${getAPIKey(req)}>${req.path}`,
        api_root: getAPIRoot(req),
        status: data.meta.status,
        code: String((data.meta.ok ? data.meta.statusText : data.code) || '').toLowerCase(),
        type: data.meta.ok ? 'success' : 'error',
        request_id: _.get(req.context, 'headers.x-request-id')
    };

    Object.keys(statusData).forEach(key => {
        statusData['action.' + key] = statusData[key];
        delete statusData[key];
    });

    let nextQuery = querystring.stringify(Object.assign(originalQueryData, statusData));
    urlComponents[2] = nextQuery ? ('?' + nextQuery) : '';

    return urlComponents.slice(1).join('');
}
