import axios, { AxiosRequestConfig, AxiosResponse, AxiosInstance } from 'axios';
import { formatMessage } from 'umi-plugin-locale';
import _ from 'lodash';
import md5 from 'md5';
import { globalState } from '@/services/common';
import { isDev, dispatch } from './utils';
import config from '@/config';

export interface AppResp<T = any> {
  ok: boolean,
  code: number;
  msg: string;
  data?: T;
}

const successLabel = 'color:white;background-color:green;padding: 1px 2px';
const failureLabel = 'color:white;background-color:red;padding: 1px 2px';
const originLabel = 'color:unset;background-color:unset;padding:unset';
const errorMap = {
  404: 'common.404',
  403: 'common.403',
};

const { baseURL } = config;
const instance = axios.create({
  baseURL,
});

function replaceId(obj: any) {
  if (obj instanceof Array) {
    obj.forEach(replaceId);
  } else if (typeof obj === 'object') {
    const id = obj['_id'];
    if (id) {
      delete obj['_id'];
      obj.id = id;
    }
    _.entries(obj).forEach(([_, value]) => {
      if (typeof value === 'object') {
        replaceId(value);
      }
    });
  }
}

async function filter<T>(
  method: keyof AxiosInstance,
  url: string,
  ...args: any[]
): Promise<AppResp<T>> {
  let ret;
  try {
    const { user } = globalState().user;
    if (user && user.token) {
      instance.defaults.headers = {
        ...(instance.defaults.headers || {}),
        token: user.token,
      };
    } else {
      instance.defaults.headers = _.omit(instance.defaults.headers, 'token');
    }
    const matches = url.match(/(?:[/\da-zA-Z]+)/) || [''];
    const timestamp = Date.now();
    instance.defaults.headers.ts = `${timestamp}`;
    instance.defaults.headers.sig = md5(`${timestamp}${matches[0]}LimitSaltLOL8080`);
    const { status, data, headers } =
      await (instance[method] as (...args: any[]) => Promise<AxiosResponse<AppResp>>)(url, ...args);
    replaceId(data);
    if (headers && headers.token) {
      dispatch({ type: 'user/updateToken', payload: headers.token });
    }
    ret = {
      ok: status >= 200 && status < 300 && data && data.code === 0,
      code: data && data.code,
      msg: data && data.msg,
      data: data && data.data,
    };
  } catch (e) {
    let code = 404;
    let msg = 'network.error';
    if (e.response) {
      code = e.response.status;
      msg = (errorMap as any)[code] || 'network.error';
      if (code === 403 || code === 401) {
        dispatch({ type: 'user/logout' });
      }
    }
    ret = {
      ok: false,
      msg: formatMessage({ id: msg }),
      code,
    };
  }
  if (isDev) {
    /* tslint:disable */
    console.group(`%c${method.toUpperCase()}%c: ${baseURL}${args[0]}`, ret.ok ? successLabel : failureLabel, originLabel);
    console.log(ret);
    console.groupEnd();
    /* tslint:enable */
  }
  return ret;
}

function groupUrl(url: string, params?: any): string {
  const postfix = _.keys(params || {})
    .map(key => `${key}=${encodeURIComponent(params[key] || '') || ''}`)
    .join('&');
  if (!postfix) return url;
  return `${url}?${postfix}`;
}

function get<T = any>(url: string, params?: any, config?: AxiosRequestConfig) {
  return filter<T>('get', groupUrl(url, params), config);
}

function post<T = any>(url: string, data?: any, config?: AxiosRequestConfig) {
  return filter<T>('post', url, data, config);
}

export default {
  get,
  post,
};

export {
  get,
  post,
};
