/**
 * 统一接口公共模块
 * @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 './Date';
 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(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 config = Container.getValue('config.tyApi');
 logger.info(`tyApi config:${JSON.stringify(config)}`);

 /**
  * 统一上行接口装饰器
  * 
  * 自动验签，仅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: 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(uuidv4(), '-', '')}`,
             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;
 
         //生产环境不记录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;
     }, error => {
         throwApiErrorFromAxiosError(error, '统一接口');
     });
 
     return axiosInstance;
 }
 
 /**
  * 统一接口axios实例
  * 
  * 如果typescript-ioc有注册配置config.tyApi，那么会自动创建该实例，否则为空
  * 
  * config.tyApi配置项参考{@link TyApiConfig}
  */
 export let axiosInstance: AxiosInstance = null;
 
 if (config) {
     axiosInstance = generateAxiosInstance(config);
 }