/**
 * 统一接口公共模块
 * @date 2022-03-04
 * @author 何华东
 */

import axios, { AxiosInstance } from 'axios';
import { logger } from "mslib"
import { CryptoUtils } from './CryptoUtils'
import Contants, { IS_DEBUG, IS_PROD, IS_TEST } from './Contants'
import DateUtils from './Date';
import { replaceAll, uuid } from './Utils';
import { ApiError, throwApiErrorFromAxiosError } from './Errors';
import { IocConfigProvider } from './Config';
import { TyApiConfig } from './Config';
import { ConfigCenterConfigProvider } from './ConfigCenter';

/**
 * 统一接口请求基类
 */
export class ApiRequestBase {
    /**
     * 车场id
     */
    parkId?: any;

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

/**
 * 统一/非标接口响应
 */
export class ApiResponse<T = any> {
    constructor(resCode: any) {
        this.resCode = resCode;
    }

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

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

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

    /**
     * 判断响应码是否为成功
     */
    static resCodeIsSuccess(resCode: string) {
        return resCode?.toString() === '0';
    }

    /**
     * 成功响应实例,resCode=0
     */
    static readonly success: ApiResponse = new ApiResponse<void>('0');

    /**
     * 创建一个失败响应，默认响应码为-1
     * @param resMsg 
     * @returns 
     */
    static failed(resMsg?: string) {
        let apiResponse = new ApiResponse('-1');
        apiResponse.resMsg = resMsg;
        return apiResponse;
    }

    /**
     * 重推响应实例,resCode=999
     */
    static readonly repush: ApiResponse = new ApiResponse<void>('999');

    static successWithData(data: any) {
        let apiResponse = new ApiResponse('0');
        apiResponse.data = data;
        return apiResponse;
    }

    static successWithMsg(resMsg: string) {
        let apiResponse = new ApiResponse('0');
        apiResponse.resMsg = resMsg;
        return apiResponse;
    }
}

/**
 * 如果统一接口响应码为非成功则抛出异常
 * @param apiResponse 
 */
export function throwIfResCodeNoSuccess(apiResponse: ApiResponse) {
    if (!ApiResponse.resCodeIsSuccess(apiResponse.resCode)) {
        throw new Error(`统一接口返回错误,返回值:${JSON.stringify(apiResponse)}`);
    }
}

/**
 * 签名工具
 */
export class SignUtils {
    /**
     * 获取签名
     */
    static getSign(data: object, appSecret: string) {
        let signStr = Object.keys(data).sort().filter(s => {
            if (data[s] === undefined || data[s] === null || data[s] === '') return false;
            if (['appId', 'key'].includes(s) || typeof data[s] === 'object' || Array.isArray(data[s])) return false;

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

        let sign = CryptoUtils.md5(signStr).toUpperCase();

        return sign;
    }
}

let tyapi_config: any = IocConfigProvider.getTyApiConfig();

/**
 * 统一上行接口装饰器
 * 
 * 对参数进行验签(仅PROD),异常捕获,记录日志,如果出现异常则自动返回失败报文
 * 
 * @param description 服务描述，仅为日志输出
 */
export function Service(description?: string, config?: { isLog?: boolean }): MethodDecorator {
    return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
        logger.info(`${Contants.SS_SERVICE_BASE_URL}${Contants.P_ID}.${target.constructor.name}/1.0`, description);

        let method = descriptor.value;

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

            try {
                //正式环境验签
                if ((IS_PROD || IS_TEST) && args[0]?.secure !== false) {
                    if (!tyapi_config?.appSecret) return ApiResponse.failed('未配置密钥,无法验签');
                    if (!args[0]?.key) return ApiResponse.failed('未签名');

                    let new_sign = SignUtils.getSign(args[0], tyapi_config.appSecret);
                    if (new_sign !== args[0].key) {
                        response = ApiResponse.failed('验签失败');
                    }
                }

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

            if (_error)
                logger.error(`${description || '统一上行接口'}'${target.constructor.name}'出现异常,请求参数:${JSON.stringify(args)},返回值:${JSON.stringify(response)}\n>>>>>`, _error instanceof ApiError ? _error.toString() : _error);
            else {
                if (!(config?.isLog === false)) logger.info(`${description || '统一上行接口'}'${target.constructor.name}',请求参数:${JSON.stringify(args)},返回值:${JSON.stringify(response)}`);
            }

            return response;
        }
    }
}

export function generateAxiosInstance(config: TyApiConfig): AxiosInstance {
    const _config = config;

    const axiosInstance = axios.create({
        baseURL: _config.baseUrl || Contants.TY_API_BASE_URL,
        timeout: _config.timeout || 1000 * 10,
    });

    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: _config.appId,
            reqId: `${replaceAll(uuid(), '-', '')}`,
            ts: `${DateUtils.getUnixTimestamp()}`,
        };

        //自动填充serviceCode
        if (!config.data.serviceCode) {
            let serviceCode = config.url.split('/').reverse().find(s => !!s);
            config.data.serviceCode = serviceCode.replace(serviceCode[0], serviceCode[0].toLowerCase());
        }

        dataWrap = Object.assign(dataWrap, config.data);
        dataWrap['key'] = SignUtils.getSign(dataWrap, _config.appSecret);

        config.data = dataWrap;

        return config;
    }, error => {
        throwApiErrorFromAxiosError(error, '统一接口');
    });

    axiosInstance.interceptors.response.use(response => {
        if (_config.isLog === false || response.config['isLog'] === false) return response;
        if (Contants.IS_PROD && response.config.url.includes('/dbproxy/')) return response;

        logger.info(`统一接口'${response.config['apiDesc'] || response.config.url}'调用成功,请求地址:${response.config.baseURL + response.config.url},请求头:${JSON.stringify(response.config.headers,
            (key, value) => {
                if (['Accept', 'Content-Type', 'User-Agent', 'Content-Length'].includes(key)) return;
                return value;
            })},请求参数:${response.config.data},返回值:${JSON.stringify(response.data)}`);

        return response;
    }, error => {
        throwApiErrorFromAxiosError(error, '统一接口');
    });

    return axiosInstance;
}

/**
 * 统一接口axios实例
 * 
 * 统一接口地址配置优先级：
 * 
 * 1.ioc配置
 * 
 * 2.配置中心
 * 
 * 3.根据环境自动配置
 */
export let axiosInstance: AxiosInstance = null;

init();

async function init() {
    try {
        let config = IocConfigProvider.getTyApiConfig();
        if (!config) return;

        //先已国内环境创建实例
        axiosInstance = generateAxiosInstance(config);
        logger.info(`统一接口axios实例创建成功,baseUrl:${config.baseUrl || Contants.TY_API_BASE_URL},appId:${config.appId}`);

        if (!config.baseUrl) {
            let remoteconfig = await ConfigCenterConfigProvider.getTyApiConfig();
            if (remoteconfig?.ty_url && !remoteconfig.ty_url.includes(Contants.TY_API_BASE_URL)) {
                config.baseUrl = remoteconfig.ty_url;
                axiosInstance = generateAxiosInstance(config);

                logger.info(`统一接口axios实例重建成功,baseUrl:${config.baseUrl},appId:${config.appId}`);
            }
        }
    } catch (error) {
        logger.error(error);
    }
}