/**
 * 科拓/统一API通用模块
 * @date 2022-03-04
 * @author 何华东
 */

import axios, { AxiosInstance, AxiosResponse } from 'axios';
import { logger } from "mslib"
import { CryptoUtils } from './CryptoUtils'
import { Contants } from './Contants'
import { getBasicConfig, getUniteApiConfig, UniteApiConfig } from './Config'
import { generateApiErrorFromAxiosError, generateApiErrorFromAxiosResponse } from './AxiosUtils'
import * as uuid from 'uuid'
import { ParamEmptyError, ValidateSignFailedError } from './Errors';
const moment = require('moment')

/**
 * API请求基类
 */
export class ApiRequestBase {
    parkId?: any;

    /**
     * 接口业务代码
     */
    serviceCode: string;
}

/**
 * API响应类
 */
export class ApiResponse<T = any> {
    constructor(resCode: any) {
        this.resCode = resCode;
    }

    /**
     * 响应码,0为成功
     */
    resCode: any;

    /**
     * 响应信息
     */
    resMsg?: string;

    /**
     * 响应数据
     */
    data?: T;

    /**
     * 判断指定的响应码是否为成功响应码
     * @param resCode 指定的响应码
     * @returns 
     */
    static resCodeIsSuccess(resCode: any) {
        return resCode?.toString() === '0';
    }

    /**
     * 成功响应对象
     */
    static readonly success: ApiResponse = new ApiResponse<void>('0');

    /**
     * 创建一个失败响应对象
     * @param resMsg 失败信息
     * @returns 
     */
    static failed(resMsg?: string) {
        let apiResponse = new ApiResponse('-1');
        apiResponse.resMsg = resMsg;
        return apiResponse;
    }

    /**
     * 创建一个成功响应对象
     * @param data 数据
     * @returns 
     */
    static successWithData(data: any) {
        let apiResponse = new ApiResponse('0');
        apiResponse.data = data;
        return apiResponse;
    }

    /**
     * 创建一个成功响应对象
     * @param resMsg 信息
     * @returns 
     */
    static successWithMsg(resMsg: string) {
        let apiResponse = new ApiResponse('0');
        apiResponse.resMsg = resMsg;
        return apiResponse;
    }
}

/**
 * 统一API默认配置
 */
export const defaultUniteApiConfig: UniteApiConfig = {
    baseUrl: Contants.UNITE_API_BASE_URL,
    timeout: 10000,
    appId: null,
    appSecret: null,
    axiosErrorHandler: (error) => {
        if (error.isAxiosError) throw generateApiErrorFromAxiosError(error);
        throw error;
    }
}

/**
 * 生成统一API axios实例
 * 
 * 默认会对请求进行拦截，自动填充默认请求参数(appId、ts等)，并签名
 * 
 * 默认会对响应进行拦截，进行日志记录
 * @type { UniteApiConfig } 用户需实现UniteApiConfig接口，并在ioc中注册
 * @returns 
 */
function generateAxiosInstance(uniteApiConfig: UniteApiConfig): AxiosInstance {
    const axiosInstance = axios.create({
        baseURL: uniteApiConfig.baseUrl,
        timeout: uniteApiConfig.timeout,
    });

    axiosInstance.interceptors.request.use(config => {
        config.headers = Object.assign({
            'Content-Type': 'application/json;charset=utf-8',
            'version': config.headers?.version || '1.0.0',
        }, config.headers);

        let dataWrap = {
            appId: uniteApiConfig.appId,
            reqId: `${uuid.v4().replace('-', '')}`,
            ts: `${moment().valueOf()}`,
            key: ''
        };

        dataWrap = Object.assign(dataWrap, config.data);

        let signStr = Object.keys(dataWrap).sort().filter(k => {
            if (['appId', 'key'].includes(k)) return false;
            if (dataWrap[k] === undefined || dataWrap[k] === null || dataWrap[k] === '') return false;
            if (typeof dataWrap[k] === 'object') return false;
            if (Array.isArray(dataWrap[k])) return false;

            return true;
        }).map(k => `${k}=${dataWrap[k]}`).join('&') + `&${uniteApiConfig.appSecret}`;

        dataWrap.key = CryptoUtils.md5(signStr).toUpperCase();

        config.data = dataWrap;

        return config;
    }, error => {
        return uniteApiConfig.axiosErrorHandler(error);
    });

    axiosInstance.interceptors.response.use(response => {
        //生产环境不记录SQL代理日志
        if (Contants.IS_PROD && response.config.url.includes('/dbproxy/')) return response;

        logger.info(`统一API'${response.config.url}'调用成功,
                    requestUrl:${response.config.baseURL + response.config.url},
                    headers:${JSON.stringify(response.config.headers)},
                    requestData:${response.config.data},
                    response:${JSON.stringify(response.data)}`);

        return response;
    }, error => {
        return uniteApiConfig.axiosErrorHandler(error);
    });

    return axiosInstance;
}

/**
 * 判断指定的统一API响应报文中的响应码是否为非成功，如果非成功则抛出ApiError异常
 * @param axiosResponse 指定的统一API响应报文
 */
export function throwIfResCodeNoSuccess(axiosResponse: AxiosResponse<ApiResponse>) {
    if (!ApiResponse.resCodeIsSuccess(axiosResponse.data?.resCode)) {
        throw generateApiErrorFromAxiosResponse(axiosResponse, '统一API返回错误');
    }
}

/**
 * 统一API上行API装饰器
 * 
 * 自动验签，异常捕获，日志记录，发生异常自动返回错误报文等
 * @param description 服务描述
 * @returns
 */
 export function Service(description?: string): MethodDecorator {
    return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
        logger.info(`${Contants.SS_SERVICE_BASE_URL}${getBasicConfig()?.projectId}.${target.constructor.name}/1.0`, description);

        let method = descriptor.value;

        descriptor.value = async function () {
            let response;
            let _error;

            try {
                if (Contants.IS_PROD) {
                    if (!arguments || !arguments[0] || !arguments[0].key) throw new ParamEmptyError();

                    let input = arguments[0];

                    let signStr = Object.keys(input).sort().filter(k => {
                        if (['appId', 'key'].includes(k)) return false;
                        if (input[k] === undefined || input[k] === null || input[k] === '') return false;
                        if (typeof input[k] === 'object') return false;
                        if (Array.isArray(input[k])) return false;

                        return true;
                    }).map(k => `${k}=${input[k]}`).join('&') + `&${getUniteApiConfig().appSecret}`;

                    let new_sign = CryptoUtils.md5(signStr).toUpperCase();
                    if (new_sign !== input.key) throw new ValidateSignFailedError();
                }

                response = await method.apply(this, arguments);
            } catch (error) {
                response = ApiResponse.failed(error instanceof Error ? error.message : error.toString());
                _error = error;
            }

            logger[_error ? 'error' : 'info'](`${target.constructor.name},input:${JSON.stringify(arguments)},response:${JSON.stringify(response)}`, _error);

            return response;
        }
    }
}

let uniteApiConfig = getUniteApiConfig();
logger.info('uniteApiConfig', uniteApiConfig);

/**
 * 统一API axios实例
 */
export const axiosInstance = generateAxiosInstance(Object.assign(defaultUniteApiConfig, uniteApiConfig));