import { serverBaseUrl } from '@/common'
import axios, {
  type AxiosRequestConfig,
  type AxiosResponse,
} from 'axios'
import * as qs from 'qs'
import { useAppUserStore } from '@/common/app/app-user-store.ts'
import Utils from '@/common/utils/index.ts'
import mime from '@/common/utils/mime.ts'
import Evt from '@/common/utils/evt.ts'
import { ElMessage } from 'element-plus'

/**
 * HTTP 统一响应结构
 */
export interface R<T = any> {
  code: number;
  msg: string;
  message: any;
  success: boolean;
  data: T;
  headers?: AxiosRequestConfig['headers']
}

type AxiosConfig = Pick<AxiosRequestConfig, 'headers' | 'params' | 'responseType' | 'paramsSerializer'>

/**
 * Query 参数处理器
 *
 * @param params 参数内容
 */
const paramsSerializer = (params: any) => {
  return qs.stringify(params, {indices: false, allowDots: true})
}

/**
 * 统一错误处理函数
 */
const errHandler = Utils.throttle(500, (r?: AxiosResponse<R<any>, any>) => {
  console.log('异常处理', r)
  ElMessage.error(r?.data?.message ?? (r?.data?.msg ?? '服务器错误'))
})

/**
 * axios 实例
 */
const httpUtil = axios.create({
  timeout: 10000,
  baseURL: serverBaseUrl,
  headers: {
    Accept: mime.JSON,
  },
})

/**
 * 配置请求拦截器
 */
httpUtil.interceptors.request.use(
  config => {
    const appUserStore = useAppUserStore()
    config.headers.Authorization = appUserStore.token
    config.formSerializer = {indexes: null}
    return config
  },
  error => {
    // TODO 请求失败日志
    console.error('HTTP 请求发送失败', error)
    return Promise.reject(error)
  },
)

/**
 * 配置响应拦截器
 */
httpUtil.interceptors.response.use(
  response => {
    // vite 代理失败时 响应码为 200 响应内容为空
    if (response.data == null) {
      response.data = {code: 5555, success: false, msg: '无响应内容', message: '无响应内容', data: null}
    } else if (response.config.responseType === 'arraybuffer') {
      const data = response.data as ArrayBufferView
      if (data.byteLength <= 0) {
        response.data = {code: 9999, success: false, msg: '文件获取失败', message: '文件获取失败', data: null}
      } else {
        const contentDisposition = response.headers['Content-Disposition'] ?? response.headers['content-disposition']
        const filename = getFileName(contentDisposition)
        // 将二进制流转为blob
        const blob = new Blob([ data ])
        response.data = {code: 0, success: true, msg: '成功', message: '文件获取成功', data: {content: blob, filename}}
      }
    }
    response.data.headers = response.headers
    return response.data.code === 0 ? Promise.resolve(response) : Promise.reject(response)
  },
  error => {
    console.error('HTTP 请求失败', error)
    if (error.response != null && error.response.status === 403) {
      Evt.emit('logout')
    }
    if (error.response != null) {
      error.response.data = {...error.response.data, headers: error.response.headers}
    } else if (error.request != null) {
      error.response = {
        data: {code: 9999, success: false, msg: '网络异常', message: '网络异常', data: null},
      }
    } else {
      error.response = {
        data: {code: 5555, success: false, msg: '请求发送失败', message: '请求发送失败', data: null},
      }
    }
    return Promise.reject(error.response)
  },
)

/**
 * GET 请求
 *
 * @param url 请求地址
 * @param params Query 参数
 * @param config 配置
 * @param disposeErr 是否处理错误响应，默认-->true
 */
export function get<T = any>(url: string, params?: any, config: AxiosConfig = {}, disposeErr: boolean = true) {
  if (config.responseType == null) {
    config.responseType = 'json'
  }
  config.params = params
  config.paramsSerializer = paramsSerializer

  return httpUtil.get<R<T>>(url, config)
    .then(({data}) => data)
    .catch(res => {
      if (disposeErr) errHandler(res)
      return Promise.reject(res as T)
    })
}

/**
 * POST 请求
 *
 * @param url 请求地址
 * @param body Body 参数
 * @param params 参数
 * @param config 配置
 * @param disposeErr 是否处理错误响应，默认-->true
 */
export function post<T>(url: string, body?: any, params?: any, config: AxiosConfig = {}, disposeErr: boolean = true) {
  if (config.responseType == null) {
    config.responseType = 'json'
  }
  config.params = params
  config.paramsSerializer = paramsSerializer
  return httpUtil.post<R<T>>(url, body, config)
    .then(({data}) => data)
    .catch(res => {
      if (disposeErr) errHandler(res)
      return Promise.reject(res)
    })
}

/**
 * POST 请求（编码表单）
 *
 * @param url 请求地址
 * @param body Body 参数
 * @param config Axios 配置
 * @param disposeErr 是否处理错误响应，默认-->true
 */
export function postForm<T>(url: string, body: any, config?: AxiosConfig, disposeErr: boolean = true) {
  return httpUtil.postForm<R<T>>(url, paramsSerializer(body),
    {
      headers: {
        ...(config?.headers ?? {}),
        'Content-Type': mime.FORM,
      },
      params: config?.params,
      responseType: config?.responseType ?? 'json',
    })
    .then(({data}) => data)
    .catch(res => {
      if (disposeErr) errHandler(res)
      return Promise.reject(res)
    })
}

/**
 * POST 请求（多部分表单）
 *
 * @param url 请求地址
 * @param body Body 参数
 * @param config Axios 配置
 * @param disposeErr 是否处理错误响应，默认-->true
 */
export function postMltForm<T>(url: string, body: any, config?: AxiosConfig, disposeErr: boolean = true) {
  return httpUtil.postForm<R<T>>(url, body,
    {
      headers: {
        ...(config?.headers ?? {}),
        'Content-Type': mime.MLT_FORM,
      },
      params: config?.params,
      responseType: config?.responseType ?? 'json',
    })
    .then(({data}) => data)
    .catch(res => {
      if (disposeErr) errHandler(res)
      return Promise.reject(res)
    })
}

function getFileName(contentDisposition: string) {
  // 检查content-disposition是否存在
  if (!contentDisposition) {
    return null
  }

  // 查找filename=部分
  const match = contentDisposition.match(/filename=(.+)/)
  if (!match || match.length < 2) {
    return null
  }

  // 提取并解码文件名
  const fileNameEncoded = match[1].trim()
  // 移除可能存在的引号
  const fileNameWithoutQuotes = fileNameEncoded.replace(/["']/g, '')
  // 解码URL编码的字符串
  return decodeURIComponent(fileNameWithoutQuotes)
}

export function getFileUrl(path?: string) {
  if (path == null || path.length <= 0) {
    return ''
  }
  const appUserStore = useAppUserStore()
  return serverBaseUrl + path + '?authorization=' + appUserStore.token
}

export default {
  get,
  post,
  postForm,
  postMltForm,
  getFileUrl,
  ins: httpUtil,
}
