import axios, {
  AxiosError,
  type AxiosInstance,
  type AxiosRequestConfig,
  type AxiosResponse,
  type InternalAxiosRequestConfig
} from 'axios'
import { cloneDeep, isFunction, merge } from 'lodash'

// 扩展axios的AxiosRequestConfig接口
declare module 'axios' {
  interface AxiosRequestConfig {
    retryCount?: number
    axiosHooks?: AxiosHooks
    requestOptions: RequestOptions
  }
}

export interface RequestData<T = any> {
  code: number
  data: T
  msg: string
  show: boolean
}

export interface RequestOptions {
  isParamsToData: boolean
  isReturnDefaultResponse: boolean
  isTransformResponse: boolean
  urlPrefix: string
  withToken: boolean
  // 是否显示报错提示，默认为显示
  showErrorMessage?: boolean
  // 防重复提交相关配置
  repeatSubmit?: {
    // 是否启用防重复提交
    enabled?: boolean
    // 时间间隔(ms)，小于此时间视为重复提交
    interval?: number
    // 错误提示信息
    message?: string
  }
}

export interface AxiosHooks {
  requestInterceptorsHook?: (
    config: AxiosRequestConfig
  ) => InternalAxiosRequestConfig | AxiosRequestConfig | Promise<InternalAxiosRequestConfig | AxiosRequestConfig>
  requestInterceptorsCatchHook?: (error: Error) => void
  responseInterceptorsHook?: <T = any>(
    response: AxiosResponse<RequestData<T>>
  ) => AxiosResponse<RequestData> | RequestData | T | Promise<AxiosResponse<RequestData> | RequestData | T>
  responseInterceptorsCatchHook?: (error: AxiosError) => void
}

export class Axios {
  private axiosInstance: AxiosInstance
  private readonly config: AxiosRequestConfig
  private readonly options: RequestOptions
  private cancelTokens = new Map<string, AbortController>()

  constructor(config: AxiosRequestConfig) {
    this.config = config
    this.options = config.requestOptions
    this.axiosInstance = axios.create(config)
    this.setupInterceptors()
  }

  /**
   * @description 获取axios实例
   */
  getAxiosInstance(): AxiosInstance {
    return this.axiosInstance
  }

  /**
   * @description 生成请求取消令牌的key
   */
  private generateCancelKey(config: AxiosRequestConfig): string {
    const { url, method, params, data } = config
    return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&')
  }

  /**
   * @description 取消单个请求
   */
  cancelRequest(config: Partial<AxiosRequestConfig>): void {
    const cancelKey = this.generateCancelKey(config as AxiosRequestConfig)
    const controller = this.cancelTokens.get(cancelKey)
    if (controller) {
      controller.abort()
      this.cancelTokens.delete(cancelKey)
    }
  }

  /**
   * @description 取消所有请求
   */
  cancelAllRequests(): void {
    this.cancelTokens.forEach((controller) => {
      controller.abort()
    })
    this.cancelTokens.clear()
  }

  /**
   * @description 设置拦截器
   */
  setupInterceptors(): void {
    const {
      requestInterceptorsHook,
      requestInterceptorsCatchHook,
      responseInterceptorsHook,
      responseInterceptorsCatchHook
    } = this.config.axiosHooks || {}

    // 请求拦截器
    this.axiosInstance.interceptors.request.use(
      (config: InternalAxiosRequestConfig) => {
        if (isFunction(requestInterceptorsHook)) {
          const result = requestInterceptorsHook(config)
          return result as InternalAxiosRequestConfig
        }
        return config
      },
      (err: Error) => {
        if (isFunction(requestInterceptorsCatchHook)) {
          requestInterceptorsCatchHook(err)
        }
        return Promise.reject(err)
      }
    )

    // 响应拦截器
    this.axiosInstance.interceptors.response.use(
      (response: AxiosResponse<RequestData>) => {
        if (isFunction(responseInterceptorsHook)) {
          const result = responseInterceptorsHook(response)
          return result as AxiosResponse<RequestData>
        }
        return response
      },
      (err: AxiosError) => {
        if (isFunction(responseInterceptorsCatchHook)) {
          responseInterceptorsCatchHook(err)
        }
        return Promise.reject(err)
      }
    )
  }

  /**
   * @description get请求
   */
  get<T = any>(config: Partial<AxiosRequestConfig>, options?: Partial<RequestOptions>): Promise<T> {
    return this.request({ ...config, method: 'GET' }, options)
  }

  /**
   * @description post请求
   */
  post<T = any>(config: Partial<AxiosRequestConfig>, options?: Partial<RequestOptions>): Promise<T> {
    return this.request({ ...config, method: 'POST' }, options)
  }

  /**
   * @description put请求
   */
  put<T = any>(config: Partial<AxiosRequestConfig>, options?: Partial<RequestOptions>): Promise<T> {
    return this.request({ ...config, method: 'PUT' }, options)
  }

  /**
   * @description delete请求
   */
  delete<T = any>(config: Partial<AxiosRequestConfig>, options?: Partial<RequestOptions>): Promise<T> {
    return this.request({ ...config, method: 'DELETE' }, options)
  }

  /**
   * @description patch请求
   */
  patch<T = any>(config: Partial<AxiosRequestConfig>, options?: Partial<RequestOptions>): Promise<T> {
    return this.request({ ...config, method: 'PATCH' }, options)
  }

  /**
   * @description 请求函数
   */
  async request<T = any>(config: Partial<AxiosRequestConfig>, options?: Partial<RequestOptions>): Promise<T> {
    // 合并配置
    const opt: RequestOptions = merge({}, this.options, options)
    const axiosConfig: AxiosRequestConfig = {
      ...cloneDeep(config),
      requestOptions: opt
    }

    // 拼接请求前缀
    const { urlPrefix } = opt
    if (urlPrefix && config.url) {
      const isFullUrl = /^https?:\/\//.test(config.url)
      if (!isFullUrl) {
        const fullUrl = config.url.startsWith(urlPrefix) ? config.url : `${urlPrefix}${config.url}`
        axiosConfig.url = fullUrl
      }
    }
    const cancelKey = this.generateCancelKey(axiosConfig)
    const controller = new AbortController()
    this.cancelTokens.set(cancelKey, controller)
    axiosConfig.signal = controller.signal
    try {
      const res = await this.axiosInstance.request<any, AxiosResponse<RequestData<T>>>(axiosConfig)
      this.cancelTokens.delete(cancelKey)
      return res as unknown as T
    } catch (err) {
      this.cancelTokens.delete(cancelKey)
      return Promise.reject(err)
    }
  }
}
