/**
 * 科拓/统一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, getConfig, 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 {
                 logger.info(`TYAPI,input:${JSON.stringify(arguments)}`);
 
                 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;
         }
     }
 }
 
 /**
  * 统一API axios实例
  */
 export const axiosInstance = generateAxiosInstance(Object.assign(defaultUniteApiConfig, getUniteApiConfig()));