import axios, {
  AxiosInstance,
  AxiosError,
  AxiosRequestConfig,
  InternalAxiosRequestConfig,
  AxiosResponse
} from 'axios'
import { ElMessage } from 'element-plus'
import { useGlobalStore } from '@/stores/modules/global'
import { useUserInfoStore } from '@/stores/modules/userInfo'
import { LOGIN_URL } from '@/utils'
import router from '@/routers'

export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
  loading?: boolean
  Authorization?: boolean
  IsError?: boolean
}

// 数据返回的接口
// 定义请求响应参数，不含data
interface Result {
  code: number
  msg: string
}

// 请求响应参数，包含data
interface ResultData<T = any> extends Result {
  data?: T
}

export let baseURL: string = '/K3Cloud'

enum RequestEnums {
  TIMEOUT = 20000, // 请求超时
  OVERDUE = 600, // 登录失败
  FAIL = 999, // 请求失败
  SUCCESS = 200, // 请求成功
  EXPIRE = 501 // token过期
}

let IsError = false

const config = {
  // 默认地址
  baseURL,
  // 设置超时时间
  timeout: RequestEnums.TIMEOUT as number,
  // 跨域时候允许携带凭证
  withCredentials: true
  // headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
  // headers: { 'Content-Type': 'application/json' }
}

class RequestHttp {
  // 定义成员变量并指定类型
  service: AxiosInstance
  public constructor(config: AxiosRequestConfig) {
    // 实例化axios
    this.service = axios.create(config)

    /**
     * 请求拦截器
     * 客户端发送请求 -> [请求拦截器] -> 服务器
     * token校验(JWT) : 接受服务器返回的token,存储到本地储存当中
     */
    this.service.interceptors.request.use(
      (config: CustomAxiosRequestConfig) => {
        const globalStore = useGlobalStore()
        if (config.headers && globalStore.token) {
          config.headers.set('Authorization', globalStore.token)
        }
        if (config?.IsError) IsError = true
        return config
      },

      // 请求报错
      (error: AxiosError) => {
        return Promise.reject(error)
      }
    ),
      /**
       * 响应拦截器
       * 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
       */
      this.service.interceptors.response.use(
        (response: AxiosResponse) => {
          const { data, config } = response // 解构
          const globalStore = useGlobalStore()
          const userInfoStore = useUserInfoStore()
          if (data.code === 501) {
            globalStore.$reset()
            userInfoStore.$reset()
            router.replace('/home')
          }
          if (IsError || data.code === 502) return data

          // 未携带token或token失效
          if (data.code === RequestEnums.OVERDUE) {
            // 登录信息失效，应跳转到登录页面，并清空本地的token
            globalStore.setToken('')
            ElMessage.error(data.msg)
            router.replace(LOGIN_URL)
            return Promise.reject(data)
          }
          // 全局错误信息拦截（防止下载文件得时候返回数据流，没有code，直接报错）
          if (data.code && data.code !== RequestEnums.SUCCESS) {
            ElMessage.error(data.msg) // 此处也可以使用组件提示报错信息
            return Promise.reject(data)
          }
          // * 成功请求（在页面上除非特殊情况，否则不用在页面处理失败逻辑）
          return data
        },
        (error: AxiosError) => {
          const { response } = error
          // 请求超时 && 网络错误单独判断，没有 response
          if (error.message.indexOf('timeout') !== -1) ElMessage.error('请求超时！请您稍后重试')
          if (error.message.indexOf('Network Error') !== -1)
            ElMessage.error('网络错误！请您稍后重试')
          // 根据响应的错误状态码，做不同的处理
          if (response) this.handleCode(response.status)
          // 服务器结果都没有返回(可能服务器错误可能客户端断网)，断网处理:可以跳转到断网页面
          if (!window.navigator.onLine) router.replace('/500')
          return Promise.reject(error)
        }
      )
  }

  handleCode(code: number): void {
    switch (code) {
      case 401:
        ElMessage.error('登录失败，请重新登录')
        break
      default:
        ElMessage.error('请求失败')
        break
    }
  }

  // 常用方法封装
  get<T>(url: string, params?: Object, _object = {}): Promise<ResultData<T>> {
    return this.service.get(url, { params, ..._object })
  }

  post<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
    return this.service.post(url, params, _object)
  }

  put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
    return this.service.put(url, params, _object)
  }

  delete<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
    return this.service.delete(url, { params, ..._object })
  }
  download(url: string, params?: object, _object = {}): Promise<BlobPart> {
    return this.service.post(url, params, { ..._object, responseType: 'blob' })
  }
}

// 导出一个实例对象
export default new RequestHttp(config)
