import axios from "axios";
import qs from "qs";

import progress from "@/service/progress";
import store from "@/store";

export const CODE_SUCCESS = 200;           // 状态正常
export const CODE_TOKEN_INVALID = 4002;    // 登录失效
export const CODE_NOT_AUTHORIZED = 4003;   // 没有权限

/////////////////////////////////////////////////  基础参数   /////////////////////////////////////////////////

const _env = process.env; // 不支持解构
const request = {};

// 默认配置: 内部变量, 兼容reject不能获取到请求conf配置, 设置全局变量记录
const _conf = {
  mContentType: false, // 记录默认请求头格式
  countLoading: 0 // 使用计数 [拦截器失败重置]
};

// 网络请求
const ContentType = [
  "application/json;charset=UTF-8",
  "application/x-www-form-urlencoded"
];

const http = axios.create({
  timeout: _env.VUE_APP_TIME_OUT,
  baseURL: _env.VUE_APP_BASE_URL
});
http.defaults.headers["Content-Type"] = ContentType[Number(_conf.mContentType)]; // 默认 Content-Type

// 拦截器专用: 处理loading状态, 避免出现闪屏; 处理是否显示错误提示
function _interceptorsError (error) {
  _conf.countLoading = 0;
  progress.hideLoading();
  const errMsg = error ? error.message ?? "系统错误" : null;
  progress.showErrorMessage(errMsg)
  return error;
}

// 请求拦截器
http.interceptors.request.use(
  config => {
    // 无需删除conf属性: 不会提交到请求
    const { conf } = config;
    if (!conf[KEY_NO_LOADING]) {
      _conf.countLoading += 1;
      progress.showLoading();
    }
    if (conf[KEY_M_CONTENT_TYPE]) {
      config.headers["Content-Type"] = ContentType[Number(!_conf.mContentType)]; // // 修改 Content-Type: 取反
    }
    // OPay临时权限
    // if (!conf[KEY_NO_LOAD_TOKEN] && process.env.NODE_ENV === "production" && !["faa5c4e1", "b33efa59", "g3dgfc93"].includes(store.state.user.token)) {
    //   setTimeout(() => {
    //     progress.invalidToken("该账户无操作权限");
    //   }, 350);
    //   return null;
    // }
    config.headers["Authorization"] = store.state.user.token;
    console.log('请求入参', config);
    return config;
  },
  error => {
    return _interceptorsError(error);
  }
);

// 将blob对象转化为json（文件类型调用ajax 取后端的返回值做特殊处理）
function _fileToJson (file) {
  let data = {},
    message = "";
  function formatReturn () {
    return { data, message };
  }
  return new Promise(resolve => {
    const reader = new FileReader();
    reader.onload = res => {
      const { result } = res.target; // 得到字符串
      try {
        // 解析成json对象
        data = JSON.parse(result);
      } catch (err) {
        message = err.message || err;
      }
      resolve(formatReturn());
    }; // 成功回调
    reader.onerror = err => {
      message = err.message || err;
      resolve(formatReturn());
    }; // 失败回调
    reader.readAsText(new Blob([file]), "utf-8"); // 按照utf-8编码解析
  });
}

// 响应拦截器
http.interceptors.response.use(
  async response => {
    console.log('请求响应', response);
    const { conf } = response.config;
    // loading处理
    if (!conf[KEY_NO_LOADING]) {
      _conf.countLoading -= 1;
    }
    if (!_conf.countLoading) {
      progress.hideLoading()
    }
    if (conf[KEY_IGNORE_RESPONSE]) {
      return response;
    }
    // 文件Excel导出: 处理请求声明的blob是否为json
    if (response.config.conf[KEY_EXPORT_TYPE]) {
      const rsp = await _fileToJson(response.data);
      // 若响应数据为流
      if (!rsp.data.code) {
        // 文件名称: 需要后端设置为可见, 否则 header 有, 但 js 取不到值
        let fileName;
        if (response.headers["content-disposition"]) {
          fileName = decodeURIComponent(response.headers["content-disposition"].replace("attachment;filename=", "").replace("attachment;filename*=utf-8", ""));
        }
        return { data: response.data, fileName }; // 格式化输出
      }
      // 赋值, 触发失败弹出框
      response.data = rsp.data;
    }
    const msg = response.data.msg || response.data.message;
    const code = Number(response.data.code);
    if (code === CODE_SUCCESS || response.data.success) {
      const tip = conf[KEY_SHOW_MESSAGE];
      if (tip) {
        // 需要show成功Message信息
        progress.showSuccessMessage(typeof tip == "boolean" ? msg || "请求成功" : tip);
      }
      return response.data
    }
    // 登录失效跳转登录页面. NOTE: 注意可能导致循环调用
    if (code === CODE_TOKEN_INVALID) {
      progress.showErrorMessage(msg)
      setTimeout(() => {
        progress.invalidToken(msg);
      }, 350);
    }
    // 请求结束忽略error
    if (!conf[KEY_NO_ERROR_TIP]) {
      progress.showErrorMessage(msg || '未知异常');
    }
    return Promise.reject(msg);
  },
  error => {
    return _interceptorsError(error);
  }
);

/////////////////////////////////////////////////  请求配置   /////////////////////////////////////////////////

/** @param Boolean: noLoading => 是否显示loading */
export const KEY_NO_LOADING = "noLoading";
/** @param Boolean: noErrorTip => 是否显示错误提示框; */
export const KEY_NO_ERROR_TIP = "noErrorTip";
/** @param Boolean: mContentType => 是否修改请求头类型 */
export const KEY_M_CONTENT_TYPE = "mContentType";
/** @param Boolean: showMessage => 是否显示提示 */
export const KEY_SHOW_MESSAGE = "showMessage";
/** @param Boolean: responseType => 导出文件流类型 */
export const KEY_EXPORT_TYPE = "responseType";
/** @param Boolean: ignoreResponse => 返回数据不校验 */
export const KEY_IGNORE_RESPONSE = "ignoreResponse";
/** @param Boolean: noLoadToken => 不传递token */
export const KEY_NO_LOAD_TOKEN = "noLoadToken";

/** 1.get: url上param, 后端取值@requestParam，也可用request.getParameterMap().get(“key”), 参数会被放入一个集合 */
request.doGet = function (url, param = {}, conf = {}) {
  const params = qs.stringify(param);
  if (params) {
    const joint = url.includes("?") ? "&" : "?";
    url += (joint + params)
  }
  return http({
    url,
    method: "GET",
    conf
  });
};

/** 2.post: body内json, 后端取值@requestBody, Map 或转为实体 */
request.doPost = function (url, param = {}, data = {}, conf = {}) {
  const params = qs.stringify(param);
  if (params) {
    const joint = url.includes("?") ? "&" : "?";
    url += (joint + params)
  }
  return http({
    url,
    method: "POST",
    data,
    conf,
    responseType: conf[KEY_EXPORT_TYPE]
  });
};

/** 3.form: body内格式为form, 和content-type有关系, 需要为form格式后端才能读取: 不能使用@RequestBody，参数会自动解析到实体; 若不是实体通过方法转Map */
request.doForm = function (url, param, data = {}, conf = {}) {
  // 变更请求头类型, 传递form
  conf[KEY_M_CONTENT_TYPE] = true;
  return this.doPost(url, param, data, conf);
};


/** 4.upload: body-formData, 一般用于文件上传, 追加数据流 */
request.doUpload = function (url, param, data = {}, conf = {}) {
  const formData = new FormData();
  Object.keys(data).forEach(prop => {
    formData.append(prop, data[prop]);
  });
  return this.doPost(url, param, formData, conf);
};

/** 5.并发多个请求优先(类方法) */
request.doSpread = function (arrReq) {
  return axios.spread(arrReq);
};

/** 6.并发多个请求全部(类方法) */
request.doAll = function (arrReq) {
  return axios.all(arrReq);
};

/////////////////////////////////////////////////  服务配置   /////////////////////////////////////////////////


/** 1.excel: 下载blob数据 */
request.doDownload = function (blobData, fileName) {
  fileName = fileName || "导出文件_" + Date.now();
  const blob = new Blob([blobData], {
    type: "application/vnd.ms-excel;charset=UTF-8"
  });
  const link = document.createElement("a");
  link.style.display = "none";
  link.href = URL.createObjectURL(blob);
  link.download = fileName;
  document.body.appendChild(link);
  link.click();
  URL.revokeObjectURL(link.href);
  document.body.removeChild(link);
};

/** 2.Excel: 导出并下载blob数据 */
request.doExport = async function (url, param, data = {}) {
  const rsp = await request.doPost(url, param, data, {
    [KEY_EXPORT_TYPE]: "blob"
  });
  // 若响应数据为流
  if (!rsp.code) {
    this.doDownload(rsp.data, rsp.fileName);
  }
};

/** 3.Excel: 自定义导入, 自动下载失败记录 */
request.doImport = async function (url, file, param, data) {
  const rsp = await request.doUpload(url, param, { file, ...data }, {
    [KEY_EXPORT_TYPE]: "blob"
  });
  // 若响应数据为流
  if (!rsp.code) {
    progress.showErrorMessage("部分导入失败");
    this.doDownload(rsp.data, rsp.fileName);
    return
  }
  progress.showSuccessMessage("全部导入成功");
}

/** 4.Excel: 导入数据el-upload组件 */
request.doElementUI = function (url, data = {}, successHandle) {
  return {
    action: url,
    fileList: [],
    data,
    onReady () {
      progress.showLoading();
    },
    success: function (rsp) {
      progress.hideLoading();
      if (rsp.code == CODE_SUCCESS) {
        progress.showSuccessMessage("导入成功");
        successHandle && successHandle(rsp)
      } else {
        progress.showErrorMessage(rsp.msg);
      }
    },
    failure: function () {
      progress.hideLoading();
      progress.showErrorMessage("导入失败");
    }
  };
};

/** @interface 导出请求方法:thunk包装调用 */
export default request;
