/**
 * 网络相关接口
 */
/* globals GLOBAL_OBJECT IS_SMALL_GAME */
import { callNative, callNativeSync, getUid, emptyFn, showSystemLog } from './util';
import { readFile } from './files';
import * as system from './system';
import * as eventHandlers from './util/eventHandlers';
import compareVersion from '../lib/compareVersion';
import { isValidOnCookie } from './util/commonFn';

/**
 * make response object for js
 * response 结构
 * { data: string, status: number, statusText: string, headers: object }
 *
 * @param {object} response native returned respons
 * @param {string} type responseType 是 json/text，如果是json，decode
 */
function makeResponse(response, type) {
    let {
        data = null, status = 0, headers = {}, errMsg
    } = response;
    let contentType = headers['Content-Type'] || headers['content-type'] || '';
    if (type === 'json' || contentType.indexOf('application/json') !== -1) {
        /* eslint-disable no-empty */
        try {
            data = JSON.parse(data);
        } catch (e) {}
        /* eslint-enable no-empty */
    }

    return {
        errMsg,
        data,
        statusCode: status,
        header: headers
    };
}

const downloadFileCallBacks = {};

export default {
    downloadFileCallBacks,
    /**
     * 发送网络请求
     *
     * @param {object} options
     * @param {string} options.url 开发者服务器接口地址
     * @param {string | object | ArrayBuffer} options.data 请求的参数
     * @param {object} [options.header = {}] 设置请求的 header，header 中不能设置 Referer。
     * @param {string} [options.method = "GET"] HTTP 请求方法
     * @param {string} [options.dataType = "json"] 返回的数据格式
     * @param {string} [options.responseType = "text"] 响应的数据类型
     * @param {function} [options.success = emptyFn] 成功回调
     * @param {function} [options.fail = emptyFn] 失败回调
     */
    request({
        url,
        data = {},
        header = {},
        method = 'GET',
        dataType = 'json',
        responseType = 'text',
        success = emptyFn,
        fail = emptyFn,
        complete = emptyFn,
    } = {}) {
        showSystemLog && console.log('bl.app.request', {
            url,
            data,
            header,
            method,
            dataType,
            responseType
        });
        if (!data) data = {};
        const callbackId = getUid();
        const successFn = typeof success === 'function' ? success : emptyFn;
        const failFn = typeof fail === 'function' ? fail : emptyFn;
        const completeFn = typeof complete === 'function' ? complete : emptyFn;

        /**
        * date          20200623
        * author        转山
        * description   支持请求header中cookie（仅支持安卓平台），对不同异常case进行拦截。
        * */
        const systemInfo = system.default.getSystemInfoSync();
        if (systemInfo && systemInfo.platform === 'android' && compareVersion(systemInfo.SDKVersion, '3.8.0') <= 0) {
            let cookieArr = [];
            // 检测所有属性不区分大小写的cookie属性
            Object.keys(header).forEach(x => {
                if (/cookie/i.test(x))  cookieArr.push(x);
            });

            if (cookieArr.length === 0) {   // 没有cookie
                header.cookie = 'defaultCookieInOldVersion=invalid';
            } else if (cookieArr.length === 1) {    // 1个cookie
                let tempResult = isValidOnCookie(header[cookieArr[0]]);
                switch (tempResult.isValid) {
                case "part":
                    console.warn('cookie部分键值异常，请检查：存在异常cookie键值对。' + tempResult.source);
                // eslint-disable-next-line no-fallthrough
                case "all":
                    header['cookie'] = tempResult.source;
                    break;
                case "not":
                    // 无效cookie
                    header.cookie = 'defaultCookieInOldVersion=invalid';
                    console.warn('cookie值异常，请检查：' + tempResult.source);
                    break;
                default:break;
                }
                if (cookieArr[0] !== 'cookie') {
                    delete header[cookieArr[0]];
                }
            } else if (cookieArr.length > 1) {  // 多个cookie
                let checkIsValid = true;
                let checkSource = true;
                let firstSource = header[cookieArr[0]];
                let checkResult = cookieArr.map(x => {
                    let temp = isValidOnCookie(header[x]);
                    checkIsValid = checkIsValid && temp.isValid === 'all';
                    checkSource = checkSource && temp.source === firstSource;
                    delete header[x];
                    return temp;
                });
                // 多个cookie合法且内容相同，处理成一个
                if (checkIsValid && checkSource) {
                    header['cookie'] = checkResult[0].source;
                } else {
                    header.cookie = 'defaultCookieInOldVersion=invalid';
                    console.warn('存在多个cookie数据，请检查！', checkResult.map(x => x.source));
                }
            }
        }

        /**
         * @constant {Boolean} IS_SMALL_GAME 通过`webpack.definePlugin`生成，判断是否为`小游戏`
         * 在`小游戏`环境下，包内文件的get请求将转发到`fs.readFile`
         */
        if (IS_SMALL_GAME && method.toLocaleUpperCase() === 'GET' && /^\.{0,2}\//.test(url)) {
            readFile({
                filePath: url,
                encoding: responseType === 'arraybuffer' ? responseType : 'utf8',
                success: (res) => {
                    successFn({
                        errMsg: 'request:ok',
                        data: res.data,
                        header: {},
                        statusCode: 200
                    });
                    completeFn({
                        errMsg: 'request:ok',
                        data: res.data,
                        header: {},
                        statusCode: 200
                    });
                },
                fail: (err) => {
                    failFn({ errMsg: err.errMsg.replace('readFile', 'request') });
                    completeFn({ errMsg: err.errMsg.replace('readFile', 'request') });
                }
            });
        } else {
            const nativeOptions = {};
            // const responseType = options.responseType === 'json' ? 'json' : 'text';
            nativeOptions.method = method.toLocaleUpperCase();

            let nativeUrl = url.replace(/#.*/, '');
            const hasSearch = nativeUrl.indexOf('?') !== -1;
            const contentType = header['Content-Type'] || header['content-type'] || '';

            if (typeof header === 'object') {
                nativeOptions.headers = header;

                if (!contentType) {
                    nativeOptions.headers['Content-Type'] = 'application/json';
                }
            } else {
                nativeOptions.headers = {
                    'Content-Type': 'application/json'
                };
            }

            if (nativeOptions.method === 'GET' || nativeOptions.method === 'HEAD') {
                let search;

                if (typeof data === 'object') {
                    search = Object.keys(data).map(key => {
                        return encodeURIComponent(key) + '=' + encodeURIComponent(data[key]);
                    }).join('&');
                } else {
                    search = data;
                }

                if (hasSearch && search) {
                    nativeUrl = nativeUrl.replace('?', '?' + search + '&');
                } else if (search) {
                    nativeUrl += '?' + search;
                }
            } else if (nativeOptions.method === 'OPTIONS' ||
                nativeOptions.method === 'POST' ||
                nativeOptions.method === 'PUT' ||
                nativeOptions.method === 'DELETE' ||
                nativeOptions.method === 'TRACE' ||
                nativeOptions.method === 'CONNECT') {
                let nativeData;
                if (typeof data === 'object') {
                    if (contentType === 'application/x-www-form-urlencoded') {
                        nativeData = Object.keys(data).map(key => {
                            return encodeURIComponent(key) + '=' + encodeURIComponent(data[key]);
                        }).join('&');
                    } else {
                        nativeData = JSON.stringify(data);
                    }
                } else {
                    nativeData = data;
                }
                nativeOptions.data = nativeData;
            }

            nativeOptions.url = nativeUrl;
            nativeOptions.responseType = responseType;
            nativeOptions.dataType = dataType;

            const callbackFn = (result) => {
                if (result.code === 0) {
                    // native ok, got http response
                    successFn(makeResponse(Object.assign({ errMsg: 'request:ok' }, result.data), dataType));
                    completeFn(makeResponse(Object.assign({ errMsg: 'request:ok' }, result.data), dataType));
                } else {
                    let _outputData = {};
                    // native error, no http response
                    if (result.data && Object.prototype.toString.call(result.data) === '[object Object]') {
                        let {
                            data = null, status = result.code || 0, headers = {}, errMsg = 'request:fail'
                        } = result.data;
                        _outputData = { data,status,headers,errMsg }
                        failFn(makeResponse(_outputData));
                    } else {
                        // 原逻辑不动
                        const _default = {
                            data : null,
                            status : result.code || 0,
                            headers : {},
                            errMsg : 'request:fail'
                        }
                        _outputData = _default;
                        failFn(makeResponse(_default));
                    }

                    completeFn(makeResponse(_outputData));
                }
            };

            GLOBAL_OBJECT.callbacks[callbackId] = callbackFn;
            GLOBAL_OBJECT.callNative('request', JSON.stringify(nativeOptions), callbackId);
        }
    },
    /**
     * 下载文件资源到本地。
     *
     * @param {Object} options
     * @param {String} options.url 下载资源的 url
     * @param {Object} options.header HTTP 请求的 Header，Header 中不能设置 Referer
     * @param {Function} options.success
     * @param {Function} options.fail
     */
    downloadFile({
        url,
        header = {},
        filePath = '',
        success = emptyFn,
        fail = emptyFn,
        complete = emptyFn
    } = {}) {
        const systemInfo = callNativeSync('getSystemInfoSync').data;

        if (systemInfo.SDKVersion < '2.3.0') {
            const options = {
                url,
                header,
                filePath,
                success,
                fail,
                complete
            };
            callNative('downloadFile', options, {
                url: 'String',
                header: 'Object'
            });
        }
        // 546起支持下载回调，换了实现方式
        else {
            const taskResult = callNativeSync('createDownloadTask', {
                url,
                header,
                filePath
            });
            let id;

            if (taskResult.code === 0) {
                id = taskResult.data.id;
            }
            else {
                fail({ errMsg: 'createDownloadTask:error' });

                return {
                    onProgressUpdate() {},
                    abort() {}
                };
            }

            const onProgressUpdate = (callback) => {
                downloadFileCallBacks[id].onProgressUpdateCallbacks.push(callback);
            };
            const abort = () => {
                callNative('abortDownloadTask', {
                    id
                });
            };

            downloadFileCallBacks[id] = {
                success,
                fail,
                onProgressUpdateCallbacks: []
            };

            return {
                onProgressUpdate,
                abort
            };
        }
    },
    /**
     * 下载文件资源到本地。
     *
     * @param {Object} options
     * @param {String} options.url 下载资源的 url
     * @param {Object} options.header HTTP 请求的 Header，Header 中不能设置 Referer
     * @param {Function} options.success
     * @param {Function} options.fail
     */
    uploadFile({
        url,
        header = {},
        filePath = '',
        name = '',
        formData = {},
        success = emptyFn,
        fail = emptyFn,
        complete = emptyFn
    } = {}) {
        const taskResult = callNativeSync('createUploadTask', {
            url,
            header,
            filePath,
            name,
            formData
        });
        let id;

        if (taskResult.code === 0) {
            id = taskResult.data.id;
        }
        else {
            fail({ errMsg: 'uploadFile:fail ' + taskResult.msg });
            complete({ errMsg: 'uploadFile:fail ' + taskResult.msg });

            return {
                onProgressUpdate() {},
                offProgressUpdate() {},
                onHeadersReceived() {},
                offHeadersReceived() {},
                abort() {}
            };
        }

        const onProgressUpdate = (callback) => {
            eventHandlers.uploadFileHandlers[id].onProgressUpdateCallbacks.push(callback);
        };
        const offProgressUpdate = (callback) => {
            const callbacks = eventHandlers.uploadFileHandlers[id].onProgressUpdateCallbacks;
            const index = callbacks.indexOf(callback);

            if (index > -1) {
                callbacks.splice(index, 1);
            }
        };
        const onHeadersReceived = (callback) => {
            eventHandlers.uploadFileHandlers[id].onHeadersReceivedCallbacks.push(callback);
        };
        const offHeadersReceived = (callback) => {
            const callbacks = eventHandlers.uploadFileHandlers[id].onHeadersReceivedCallbacks;
            const index = callbacks.indexOf(callback);

            if (index > -1) {
                callbacks.splice(index, 1);
            }
        };
        const abort = () => {
            callNative('abortUploadTask', {
                id
            });
        };

        eventHandlers.uploadFileHandlers[id] = {
            success,
            fail,
            complete,
            onProgressUpdateCallbacks: [],
            offProgressUpdateCallbacks: [],
            onHeadersReceivedCallbacks: [],
            offHeadersReceivedCallbacks: []
        };

        return {
            onProgressUpdate,
            offProgressUpdate,
            onHeadersReceived,
            offHeadersReceived,
            abort
        };
    }
};
