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

import axios, { AxiosInstance, AxiosResponse } from 'axios';
import { logger } from "mslib"
import { CryptoUtils } from './CryptoUtils'
import Contants from './Contants'
import { v4 as uuidv4 } from 'uuid';
import DateUtils from './DateUtils';
import { replaceAll } from './Utils';
import { ApiError, throwApiErrorFromAxiosError } from './Errors';
import { Container } from 'typescript-ioc';
import { TyApiConfig } from './Config';

/**
 * 统一接口请求基类
 */
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(k => {
            if (['appId', 'key'].includes(k)) return false;
            if (data[k] === undefined || data[k] === null || data[k] === '') return false;
            if (typeof data[k] === 'object') return false;
            if (Array.isArray(data[k])) return false;

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

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

        return sign;
    }
}

let config = Container.getValue('config.tyApi');

/**
 * 统一上行接口装饰器
 * 
 * 自动验签，仅PROD环境才验签，如果未签名则直接返回，不记录日志
 * 
 * 自动捕获异常并返回错误报文
 * 
 * 自动记录日志，记录请求参数及返回报文
 * @param description 服务描述，仅为日志输出，提高可读性
 */
export function Service(description?: string): 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 {
                let validate_sign = true;

                if (Contants.IS_PROD) {
                    if (!args[0]?.key) return ApiResponse.failed('未签名');

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

                if (validate_sign) response = await method.apply(this, args);
            } catch (error) {
                if (error instanceof Error) response = ApiResponse.failed(error.message);
                else if (error instanceof ApiError) response = ApiResponse.failed(error.message);
                else response = ApiResponse.failed(error.toString());

                _error = error;
            }

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

            return response;
        }
    }
}

export function generateAxiosInstance(config: {
    /**
     * 统一接口根地址
     */
    baseUrl?: string;

    /**
     * 统一接口请求超时时间
     */
    timeout?: number;

    /**
     * appId
     */
    appId: any;

    /**
     * appSecret
     */
    appSecret: string;

    /**
     * axios异常处理
     */
    axiosErrorHandler?: (error: any) => any;

    /**
     * axios响应处理
     */
    axiosResponseHandler?: (response: AxiosResponse<any>) => AxiosResponse<any> | Promise<AxiosResponse<any>>
}): AxiosInstance {
    const defaults = {
        baseURL: Contants.TY_API_BASE_URL,
        timeout: 10000,
        axiosErrorHandler: error => { throwApiErrorFromAxiosError(error, '统一接口'); },
        axiosResponseHandler: response => {
            //生产环境不记录SQL通道接口调用日志
            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;
        }
    };

    let _config = Object.assign(defaults, config);

    const axiosInstance = axios.create({
        baseURL: _config.baseUrl || defaults.baseURL,
        timeout: _config.timeout || defaults.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: _config.appId,
            reqId: `${replaceAll(uuidv4(), '-', '')}`,
            ts: `${DateUtils.getUnixTimestamp()}`,
        };

        //自动填充serviceCode
        if (!config.data.serviceCode) {
            let serviceCode = config.url.split('/').reverse().find(s => !!s);
            if (serviceCode) {
                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 => {
        if (typeof _config.axiosErrorHandler === 'function') return _config.axiosErrorHandler(error);
        defaults.axiosErrorHandler(error);
    });

    axiosInstance.interceptors.response.use(response => {
        if (typeof _config.axiosResponseHandler === 'function') return _config.axiosResponseHandler(response) || response;
        return defaults.axiosResponseHandler(response);;
    }, error => {
        if (typeof _config.axiosErrorHandler === 'function') return _config.axiosErrorHandler(error);
        defaults.axiosErrorHandler(error);
    });

    return axiosInstance;
}

/**
 * 统一接口axios实例
 * 
 * 如果typescript-ioc有注册配置config.tyApi，那么会自动创建该实例，否则为空
 * 
 * config.tyApi配置项参考{@link TyApiConfig}
 */
export let axiosInstance: AxiosInstance = null;

if (config) {
    axiosInstance = generateAxiosInstance(config);
}