/*
 * @Author: Sky.Sun 
 * @Date: 2018-07-11 15:09:50 
 * @Last Modified by: Sky.Sun
 * @Last Modified time: 2019-02-28 18:11:20
 */
const httpProxy = require('http-proxy');
const cacheClient = require('./cache').cacheClient;
const configPath = require('./getConfigPath')();
const config = require(configPath);
const settings = require('./settings');
const debugMode = require('./debugMode');
const log4js = require('./lib/log4js');
const logger = log4js.getLogger('noginx-webui');
const https = require("https");
const url = require('url');

const proxy = httpProxy.createProxyServer({
    xfwd: true,
    secure: false,
    preserveHeaderKeyCase: true,
    proxyTimeout: config.proxyTimeout
});

/**
 * 代理转发的错误处理
 */
proxy.on('error', (err, req, res) => {
    if (req) {
        logger.error(`Proxy Server Error! URL: ${req.protocol}://${req.get('Host')}${req.originalUrl} Error: ${err.message}`, req);
        if (req.query[debugMode.debugParam] === 'true') {
            const html = debugMode.getDebugHtml('Internal Server Error', debugMode.getLogArray(res));
            res.send(html);
        } else {
            res.sendStatus(500);
        }
    } else {
        logger.error(`Proxy Server Error! Error: ${err.message}`);
    }
});

/**
 * 接收到转发服务器响应的后续处理
 */
proxy.on('proxyRes', (proxyRes, req, res) => {
    const serverLog = res.get('X-Server-Log');

    // URL 中存在调试参数
    if (req.query[debugMode.debugParam] === 'true') {
        // 判断是否是 html 类型响应,或者 404/500
        const mimeType = proxyRes.headers['Content-Type'] || proxyRes.headers['content-type'] || '';
        if (mimeType.includes('text/html') || ((proxyRes.statusCode === 404 || proxyRes.statusCode === 500) && mimeType.includes('text/plain'))) {
            // 修正响应头为200,否则 nginx 会处理 404/500 错误而丢弃响应内容
            proxyRes.statusCode = 200;

            // 尝试获取所有后端日志
            let logs = [];
            if (serverLog) {
                let logData = JSON.parse(decodeURIComponent(serverLog));
                const currentHeader = proxyRes.headers['X-Server-Log'] || proxyRes.headers['x-server-log'];
                if (currentHeader) {
                    logs = JSON.parse(decodeURIComponent(currentHeader));
                    logs = logData.concat(logs);
                } else {
                    logs = logData;
                }
            }

            // 保存原始方法
            const _writeHead = res.writeHead;
            let _writeHeadArgs;
            const _end = res.end;
            let body = '';

            proxyRes.on('data', (data) => {
                data = data.toString();
                body += data;
            });

            // 重写内置方法
            res.writeHead = (...writeHeadArgs) => {
                _writeHeadArgs = writeHeadArgs;
            };
            res.write = () => {};
            res.end = (...endArgs) => {
                const output = debugMode.getDebugHtml(body, logs);

                // 一定要重新设置 Content-Length,且不能使用 output.length,因为可能包含中文
                if (proxyRes.headers && proxyRes.headers['content-length']) {
                    res.setHeader('content-length', Buffer.byteLength(output));
                }

                res.setHeader('content-type', 'text/html; charset=utf-8');

                res.setHeader('transfer-encoding', '');

                res.setHeader('cache-control', 'no-cache');

                _writeHead.apply(res, _writeHeadArgs);

                if (body.length) {
                    _end.apply(res, [output]);
                } else {
                    _end.apply(res, endArgs);
                }
            }
        }
    }

    // 如果 proxy 存在日志头且有数据,且客户端安装了 ServerLog 并开启了日志监听
    if (serverLog && req.headers['request-server-log'] === 'enabled') {
        let logData = JSON.parse(decodeURIComponent(serverLog));

        /**
         * 转发到的服务器(如 node_pro)返回的日志头
         * 这里要兼容下大小写
         */
        const currentHeader = proxyRes.headers['X-Server-Log'] || proxyRes.headers['x-server-log'];

        let updateHeader = '';
        if (currentHeader) {
            updateHeader = JSON.parse(decodeURIComponent(currentHeader));
            updateHeader = logData.concat(updateHeader);
            updateHeader = encodeURIComponent(JSON.stringify(updateHeader));
        } else {
            updateHeader = encodeURIComponent(JSON.stringify(logData));
        }

        proxyRes.headers['X-Server-Log'] = updateHeader;
    }

    // 如果 redisKey 存在,且返回状态码为 200,且内容类型为 html,且接口调用正常,才设置缓存
    const mimeType = proxyRes.headers['Content-Type'] || proxyRes.headers['content-type'] || '';
    const hasServiceError = proxyRes.headers['X-Service-Error'] || proxyRes.headers['x-service-error'];
    if (req.redisKey && proxyRes.statusCode === 200 && mimeType.includes('text/html')) {
        if (hasServiceError) {
            logger.warn('服务端存在接口调用异常,本次数据将不会缓存!');
        } else if (req.query[debugMode.debugParam] === 'true') {
            logger.warn('调试模式不设置缓存!');
        } else {
            let resBody = '';
            proxyRes.on('data', chunk => {
                resBody += chunk.toString();
            });
            proxyRes.on('end', () => {
                cacheClient.set(req.redisKey, resBody, 'EX', req.redisExpired, err => {
                    if (err) {
                        logger.error('Redis Set Error:', err.message);
                    }
                });
            })
        }
    }
});

/**
 * 生成随机数
 *
 * @param {Number} min 随机数下限
 * @param {Number} max 随机数上限
 * @returns
 */
function rand(min, max) {
    return Math.random() * (max - min) + min;
}

/**
 * 按权重随机选取一项
 *
 * @param {Array} list 要随机的数组
 * @param {Array} weight 权重数组
 * @returns
 */
function getRandomItem(list, weight) {
    // 必须是非空且数量一致的数组
    if (Array.isArray(list) && Array.isArray(weight) && list.length > 0 && list.length === weight.length) {
        // 权重数组必须都是数字
        if (weight.every(t => typeof t === 'number')) {
            const totalWeight = weight.reduce((prev, cur) => {
                return prev + cur;
            });

            const randomNum = rand(0, totalWeight);
            let weightSum = 0;

            for (let i = 0; i < list.length; i++) {
                weightSum += weight[i];
                if (randomNum <= weightSum) {
                    return list[i];
                }
            }
        } else {
            return null;
        }
    } else {
        return null;
    }
}

/**
 * 根据配置的服务器权重分配服务器转发
 *
 * @param {object} { req, res, serverId, serverName, logMsg }
 * @returns
 */
function proxyWeb({
    req,
    res,
    serverId,
    serverName,
    logMsg
}) {
    const servers = settings.getServers();
    let server;
    if (serverId) {
        server = servers.find(t => t.id === serverId);
    } else if (serverName) {
        server = servers.find(t => t.name === serverName);
    }
    if (server) {
        logMsg += `转发至${server.name}`;
        const {
            addresses,
            name: host
        } = server;
        if (addresses && Array.isArray(addresses) && addresses.length > 0) {
            const list = addresses.map(t => t.address);
            const weight = addresses.map(t => Number(t.weight));
            const proxyAddress = getRandomItem(list, weight, req);

            // 如果没有获取到随机服务器,说明配置异常
            if (!proxyAddress) {
                logMsg += ` --> 错误:list或weight数据格式不正确!list: ${JSON.stringify(list)} weight: ${JSON.stringify(weight)}`
                logger.error(logMsg, req);
                res.sendStatus(500);
                return;
            }
            const {
                protocol
            } = url.parse(proxyAddress);
            const option = {
                target: proxyAddress,
                preserveHeaderKeyCase: true,
            };
            // proxy 参数
            switch (protocol) {
                case 'https:':
                    option.agent = https.globalAgent;
                    option.headers = {
                        host
                    };
                    break;

                default:
                    break;
            }


            // URL 中存在调试参数,设置为允许修改响应
            if (req.query[debugMode.debugParam] === 'true') {
                option.selfHandleResponse = true;
            }

            logMsg += ` (host: ${proxyAddress})`;
            setImmediate(() => {
                logger.info(logMsg, req);
            })

            // 进行转发
            proxy.web(req, res, option);
        } else {
            logMsg += ` --> 错误:服务器${serverName}配置异常!servers: ${JSON.stringify(servers)}`;
            logger.error(logMsg, req);
            res.sendStatus(500);
        }
    } else {
        logMsg += `错误:未找到服务器!serverId: ${serverId}, serverName: ${serverName}, servers: ${JSON.stringify(servers)}`;
        logger.error(logMsg, req);
        res.sendStatus(500);
    }
}

module.exports = proxyWeb;