import base from './base';

const ErrorName = 'APIError';

/**
 * @typedef {Object} ErrInfo 报错信息
 * @property {number} code 错误码
 * @property {string} msg 错误信息
 */
/**
 * @typedef {ErrInfo|string} ErrMsg 错误信息
 */
/**
 * @typedef {Object} ErrRes
 * @property {number} code 错误码
 * @property {string} msg 可读错误信息
 * @property {sting} api 报错接口
 */
/**
 * @typedef {Object} APIErrInfo 
 * @property {string} message 字符串错误信息
 * @property {number} code 错误码
 * @property {string} msg 可读错误信息
 * @property {sting} api 报错接口
 * @property {any} origin 原始信息
 */
const errManager = {
  _isObj(obj) {
    return typeof obj === 'object';
  },
  _getCode(obj, errInfo) {
    const { code: ncode } = errInfo || {};
    if (base.isNumber(ncode)) return ncode;
    const { res, code } = obj || {};
    if (base.isNumber(res)) return res;
    return code;
  },
  _getApi(obj, api) {
    const { api: sapi } = obj || {};
    if (base.isEmpty(sapi)) return base.toStr(api);
    if (base.isEmpty(api)) return base.toStr(sapi);
    return `${api} ${sapi}`;
  },
  _getMsg(err, errInfo) {
    const { msg: nmsg } = errInfo || {};
    if (!base.isEmpty(nmsg)) return base.toStr(nmsg);
    const { msg, message } = err || {};
    return base.toStr(msg || message);
  },
  _toObj(str) {
    if (this._isObj(str)) return str;
    try {
      const res = JSON.parse(str);
      return this._isObj(res) ? res : null;
    } catch (err) {
      return null;
    }
  },
  _getStack(err, msg) {
    const { stack, message } = err || {};
    if (stack) {
      return stack.replace(message, msg).replace(/^Error/, ErrorName)
    }
    return ''
  },
  /**
   * errMsg 统一格式
   * @param {ErrMsg} errMsg 
   * @returns {ErrInfo}
   */
  _fixErrMsg(errMsg) {
    if (base.isString(errMsg)) {
      return {
        msg: errMsg
      }
    }
    const { msg, code } = base.toObject(errMsg, {});
    return {
      code: base.isNumber(code) ? code : null,
      msg,
    }
  },
  _mergeObj(target, obj) {
    const temp = { ...target };
    for (let i in obj) {
      if (!base.isEmpty(obj[i])) {
        temp[i] = obj[i]
      }
    }
    return temp
  },

  /**
   * 结构转换
   * @param {ErrRes} res 
   * @param {any} origin 
   * @returns {APIErrInfo}
   */
  _toAPIErrorInfo(res, origin) {
    // 统一过滤部分字段，不会展示到 message 里
    const { api, msg, code, errcode, serialize, stack } = res;
    const temp = {}
    if (!base.isEmpty(api)) temp.api = api;
    if (!base.isEmpty(msg)) temp.msg = msg;
    if (!base.isEmpty(code)) temp.code = code;
    if (!base.isEmpty(errcode)) temp.errcode = errcode;
    if (!base.isEmpty(serialize)) temp.serialize = serialize;
    const message = base.toStr(temp);
    const nstack = this._getStack(origin, message) || stack;

    return {
      ...res,
      msg: !base.isNull(msg) ? msg : message,
      message,
      stack: nstack,
      origin,
    }
  },

  /**
   * 对象转 Error
   * 从obj 中提取 api/code/msg
   * @param {Object} obj 要处理对象
   * @param {ErrInfo} errInfo 补充报错信息
   * @param {string} api API
   * @param {boolean} extractMsg 是否进一步提取 msg
   * @returns {APIErrInfo}
   */
  objToError(obj, errInfo, api, extractMsg) {
    let target = obj ? { ...obj } : {};

    // 尝试读取 target.msg 的信息到 target
    if (extractMsg) {
      const temp = this._toObj(target.msg);
      if (temp) {
        // target.msg = '';
        target = this._mergeObj(target, temp);
      }
    }

    // 获取 api、code 和 msg
    target.api = this._getApi(target, api);
    target.code = this._getCode(target, errInfo);
    target.msg = this._getMsg(target, errInfo);

    return this._toAPIErrorInfo(target, obj);
  },

  /**
   * 字符及其他串转 APIErrorConifg
   * @param {string} obj 要处理字符串
   * @param {ErrInfo} errInfo 补充报错信息
   * @param {string} api API
   * @returns {APIErrInfo}
   */
  strToError(str, errInfo, api) {
    const obj = this._toObj(str);
    // str 可转对象 
    if (obj) {
      const result = this.objToError(obj, errInfo, api);
      return {
        ...result,
        origin: str,
      }
    }

    // 获取 message
    const res = {
      api: this._getApi({}, api),
      code: this._getCode({}, errInfo),
      msg: this._getMsg({ msg: str }, errInfo),
    }
    return this._toAPIErrorInfo(res, str);
  },

  /**
   * Error 转 APIErrorConifg
   * @param {Error} err 要处理Error 对象
   * @param {ErrInfo} errInfo 补充报错信息
   * @param {string} api API
   * @returns {APIErrInfo}
   */
  errToError(err, errInfo, api) {
    // 存储除了 message 之外的 err信息
    const { message, ...errExtra } = err;

    // err.message 可转对象 
    const obj = this._toObj(err.message);
    if (obj) {
      const temp = this._mergeObj(obj, errExtra);
      const result = this.objToError(temp, errInfo, api);
      return {
        ...result,
        stack: this._getStack(err, result.message),
        origin: err
      }
    }

    // 获取 message
    const res = {
      ...errExtra,
      api: this._getApi(err, api),
      code: this._getCode(err, errInfo),
      msg: this._getMsg(err, errInfo),
    };
    return this._toAPIErrorInfo(res, err);
  },

  /**
   * APIError 转 APIErrorConifg
   * @param {APIError} err 要处理APIError 对象
   * @param {ErrInfo} errInfo 补充报错信息
   * @param {string} api API
   * @returns {APIErrInfo}
   */
  apiErrorToError(err, errInfo, api) {
    const { name, origin, ...extra } = err;
    const result = this.errToError(extra, errInfo, api);
    return {
      ...result,
      name,
      origin,
    }
  },

  /**
   * 错误处理
   */
  renderErr(err, errMsg, api, render) {
    try {
      const errInfo = this._fixErrMsg(errMsg);

      // render error
      if (render) {
        err = render(err, api, errInfo);
      }

      // APIError
      if (err && err.name === ErrorName) {
        return this.apiErrorToError(err, errInfo, api);
      }

      // Error
      if (Object.prototype.toString.call(err) === '[object Error]') {
        return this.errToError(err, errInfo, api);
      }

      // Object
      if (this._isObj(err)) {
        return this.objToError(err, errInfo, api, true);
      }

      // 默认转换
      return this.strToError(err, errInfo, api);
    } catch (e) {
      // 报错处理异常
      return this._toAPIErrorInfo({
        api,
        msg: e.message,
        errcode: 1999,
        _in: true
      }, err)
    }
  },
}

/**
 * @class APIError
 * @member {string} message 错误信息，可展示信息
 * @member {number} code 错误码
 * @member {string} path 报错API
 * @member {string} msg 系统错误信息
 * @member {any} origin 原始信息
 */
export class APIError extends Error {
  constructor(err, errMsg, path, render) {
    if (err) {
      const { message, stack, ...info } = errManager.renderErr(err, errMsg, path, render);
      super(message);

      // 复制属性
      for (let key in info) {
        this[key] = info[key]
      }

      // 确定属性
      this.name = ErrorName;
      this.stack = stack || this.stack;
    } else {
      super();
      this.name = ErrorName;
    }
  }
}

/**
 * 报错封装
 * @returns APIError
 */
export default {
  /**
   * @callback Processer 错误处理方法
   * @param {any} err 报错信息
   * @param {string} path `${module}.${api}`
   */
  /**
   * 注册error处理方法
   * @param {Processer} fun 
   */
  registErrProcess(fun) {
    if (base.isFun(fun)) { this._render = fun }
  },

  /**
   * 错误信息封装
   * @param {APIError|Error|string|Object} err 错误信息
   * @param {ErrMsg} errMsg 可展示错误信息
   * @param {string} module 模块名
   * @param {string} api API
   * @returns {APIError}
   */
  pkgErr(err, errMsg, module, api) {
    const path = api ? (module ? `${module}.${api}` : `${api}`) : ''
    return new APIError(err, errMsg, path, this._render);
  },

  /**
   * 异步报错堆栈捕捉
   * @summary 方法调用后报错的堆栈从此开始收集
   * @param {any} func 要处理的方法
   */
  catchErrStack(func, target, logger) {
    return async function wrapped(...args) {
      const error = new APIError();
      Error.captureStackTrace && Error.captureStackTrace(error, wrapped);

      try {
        return await func.apply(target || this, args);
      } catch (ex) {
        error.message = ex.message
        ex.stack = error.stack;
        logger && logger.warn(ex)
        throw ex;
      }
    }
  },

  /**
   * 同步方法报错堆栈捕捉
   * @summary 方法调用后报错的堆栈从此开始收集
   * @param {any} func 要处理的方法
   */
  catchSyncErrStack(func, target, logger) {
    return function wrapped(...args) {
      const error = new APIError();
      Error.captureStackTrace && Error.captureStackTrace(error, wrapped);

      try {
        return func.apply(target || this, args);
      } catch (ex) {
        error.message = ex.message
        ex.stack = error.stack;
        logger && logger.warn(ex)
        throw ex;
      }
    }
  },

  /**
   * 对模块下所有接口的报错堆栈处理
   * @param {Object} module 模块
   * @param {any} logger 
   */
  catchModuleErrStack(module, logger) {
    module && Object.keys(module).map(api => {
      module[api] = this.catchErrStack(module[api], module, logger)
    })
    return module
  }
}