import Taro from '@tarojs/taro';
import { PUBLIC_HEADER } from '../constants/app';
import packageJson from '../../package.json';
import {
  getOrCreateLocalId,
  getXpClientString,
  getStamp,
  makeSign,
  toBase64,
  getStorage,
  objectSort,
} from '../utils/tools';
import { _XP_SECRET, _UID } from '../constants/api';
import { jumpTo } from './jump';

const CODE_SUCCESS = 200
const CODE_AUTH_EXPIRED_ARRAY = [12010301, 12010302, 12010303, 12010304, 12010305, 12010306, 12010320, 12010002];
// 12010301, "登录状态已过期，请重新登录", "otp缓存已过期"),
// 12010302, "登录状态已过期，请重新登录", "otp已过期"),
// 12010303, "登录状态已过期，请重新登录", "otp不能为空"),
// 12010304, "登录状态已过期，请重新登录", "otp校验失败"),
// 12010305, "登录状态已过期，请重新登录", "otp签名校验失败"
// 12010306, "登录状态已过期，请重新登录", "otp校验参数失败"),
// 12010320, "登录状态已过期，请重新登录", "otp校验失败"),
// 12010002, "用户不存在，请确认后重试","可能正式跟预发的otp串了，一般用户不会接触到这问题，但还是要做处理"),


/**
 * 封装网络请求
 * // NOTE 需要注意 RN 不支持 *StorageSync，此处用 async/await 解决
 * @param {*} options
 * @needLogin 是否需要校验登录态（所有SSO的接口都需要设为true），因算签名相对比较耗性能，不需要就不计算
 */
export default async function fetch(options: any, needLogin?: boolean) {
  const { url, method = 'POST', showToast = true, autoLogin = true, skipException = false, originResponse = false } = options;

  // 对payload进行排序，保证 payload 里面的顺序和生成签名时 payload 的顺序一致，因为后端要拿 payload 计算签名，所以顺序是必须一致的，同时，后端也需要对 payload 进行排序，后端通过 XpClientCheck 注解
  if(needLogin) {
    options.payload = objectSort(options.payload, {keyUpperCase: false})
  }

  const payload = options.payload;

  const header = await buildHeader(options, needLogin)

  if (!header) return Promise.reject({ code: -1, msg: '网络不可用' })

  return Taro.request({
    url,
    method,
    data: payload,
    header
  })
    .then(async (res) => {
      // res为包含cookies、header的经过小程序封装的数据
      // 第一层data为接口返回数据，包含接口状态码
      // 第二层data一般为业务数据
      const { data } = res.data;
      let { code } = res.data;
      code = code ? code : res.data.status;

      if (skipException) { // 跳过异常处理
        const authExpired = CODE_AUTH_EXPIRED_ARRAY.indexOf(code) !== -1;
        if (authExpired && autoLogin && needLogin) { // 登录相关的异常除外
          return Promise.reject(res.data)
        }
        return res.data;
      }

      // 需要获取初始数据
      if (originResponse) {
        return res;
      }

      if (code && code !== CODE_SUCCESS) {
        return Promise.reject(res.data);
      }

      // 此为接口返回业务数据
      return data
    })
    .catch((err) => {
      console.log('request err:', url);
      console.log(err);

      let errData = err;
      if (process.env.TARO_ENV === 'alipay' && errData.status && errData.status !== 200 && errData.data) {
        errData = errData.data;
        if (skipException) { // 跳过异常处理
          const code = errData.code ? errData.code : errData.status;
          const authExpired = CODE_AUTH_EXPIRED_ARRAY.indexOf(code) !== -1;
          if (!(authExpired && autoLogin && needLogin)) { // 登录相关的异常除外
            return errData;
          }
        }
      }

      const authExpired = CODE_AUTH_EXPIRED_ARRAY.indexOf(errData.code) !== -1;
      const defaultMsg = authExpired ? '登录过期' : errData.msg || '网络异常';
      const search = (payload && payload.targetAppId) ? `?appId=${payload.targetAppId}` : '';

      if (showToast) {
        Taro.showToast({ title: defaultMsg, icon: 'none' });
      }
      if (authExpired && autoLogin && needLogin) {
        jumpTo({ url: `/pages/user/login${search}` });
      }

      return Promise.reject(errData)
    });
}

async function buildHeader(options, needLogin) {
  const { method = 'POST', payload, extraHeader = {} } = options;
  const header = Object.assign({}, PUBLIC_HEADER);

  // 处理header
  // 获取网络状态
  const networkTypeRes = await Taro.getNetworkType()
  const network = networkTypeRes.errMsg === 'getNetworkType:ok' ? networkTypeRes.networkType : 'unknow';

  if (networkTypeRes.networkType === 'none') {
    Taro.showToast({
      title: '网络不可用，请检查',
      icon: 'none',
    })
    return false;
  }

  // 获取本地的设备码，无则随机生成一个
  const di = await getOrCreateLocalId()

  // 组装计算XP-Client
  const si = Taro.getSystemInfoSync() || {}; // 此处如果断网情况下会返回 undifined
  si.model = si.model.replace('<', '').replace('>', '').replace(',', '');
  const xpClient = {
    di,
    ve: packageJson.version,
    pn: 'mini_program_xiaopeng',
    br: si.model,
    mo: si.model,
    sv: si.system,
    nt: network,
    t: new Date().getTime(),
    st: si.platform.includes('ios') ? '2' : '1',
    re: `${si.screenWidth}*${si.screenHeight}`,
  }

  header['XP-Client'] = getXpClientString(xpClient);
  header['XP-Nonce'] = getStamp();

  // 获取uid
  const uid = await getStorage(_UID) || '-1';
  header['XP-Uid'] = uid.toString();

  // 获取设备id
  header['XP-Deviceid'] = Taro.getStorageSync('wxOpenId') || 'default_device_id';

  let extraData = {};

  if (extraHeader && typeof extraHeader === 'object' && Object.keys(extraHeader).length > 0) {
    Object.keys(extraHeader).forEach(key => {
      header[key] = extraHeader[key];
    });
    extraData = extraHeader;
  }

  if (needLogin) {
    // 获取otp
    const otp = await getStorage('otp') || '';
    header['XP-Sort-Payload'] = '1' // 告诉后端payload已经排序，后端也需要排序,解决小红书 iOS 对 payload 顺序进行了调整的问题

    // 第一次算签名
    const midData = {
      'XP-Uid': header['XP-Uid'],
      // 'Content-Type': PUBLIC_HEADER['Content-Type'],
      // 'XP-Authorization': PUBLIC_HEADER['XP-Authorization'],
      'XP-Encryption-Type': PUBLIC_HEADER['XP-Encryption-Type'],
      'XP-Client-Type': PUBLIC_HEADER['XP-Client-Type'],
      'XP-Appid': PUBLIC_HEADER['XP-Appid'],
      'XP-Client': header['XP-Client'],
      'XP-Client-Encoding': PUBLIC_HEADER['XP-Client-Encoding'],
      'XP-Nonce': header['XP-Nonce'],
      // 'XP-Signature': PUBLIC_HEADER['XP-Signature'],
      'XP-Deviceid': header['XP-Deviceid'],
      'XP-Sort-Payload': header['XP-Sort-Payload'], 
      ...extraData,
    }
    const sign_1st = makeSign(method, midData, payload, _XP_SECRET);
    const author = toBase64({ xpSign: sign_1st, otp });
    header['XP-Authorization'] = author;



    // 第二次算签名
    const lastData = {
      'XP-Uid': header['XP-Uid'],
      // 'Content-Type': PUBLIC_HEADER['Content-Type'],
      'XP-Authorization': header['XP-Authorization'],
      'XP-Encryption-Type': PUBLIC_HEADER['XP-Encryption-Type'],
      'XP-Client-Type': PUBLIC_HEADER['XP-Client-Type'],
      'XP-Appid': PUBLIC_HEADER['XP-Appid'],
      'XP-Client': header['XP-Client'],
      'XP-Client-Encoding': PUBLIC_HEADER['XP-Client-Encoding'],
      'XP-Nonce': header['XP-Nonce'],
      // 'XP-Signature': PUBLIC_HEADER['XP-Signature'],
      'XP-Deviceid': header['XP-Deviceid'],
      'XP-Sort-Payload': header['XP-Sort-Payload'],
      ...extraData,
    }
    const sign_2nd = makeSign(method, lastData, payload, _XP_SECRET);
    header['XP-Signature'] = sign_2nd;
  }

  return header;
}
