import axios, { AxiosInstance, AxiosResponse, AxiosError, AxiosRequestConfig } from 'axios';
import { ElMessage, ElMessageBox, ElLoading, LoadingOptionsResolved } from 'element-plus';
import { Session, Local, Cookie } from '@/utils/storage';
import router from '@/router/index';
// 重复请求队列
const pendingMap = new Map();
// 全局loading
const LoadingInstance = {
	_target: null as any,
	_count: 0,
};
type customOptionsType = {
	repeat_request_cancel?: boolean; // 是否开启取消重复请求, 默认为 true
	loading?: boolean; // 是否开启全屏loading层效果, 默认为false
	reduce_data_format?: boolean; // 是否开启简洁的数据结构响应 减少一层data, 默认为true
	error_message_show?: boolean; // 是否开启接口错误信息展示,默认为true
	code_message_show?: boolean; // 是否开启code不为0时的信息提示, 默认为false
};
export default function myAxios(axiosConfig: any, customOptions?: customOptionsType, loadingOptions?: LoadingOptionsResolved) {
	// 配置新建一个 axios 实例
	const service: AxiosInstance = axios.create({
		baseURL: import.meta.env.VITE_API_URL,
		timeout: 60000,
		headers: { 'Content-Type': 'application/json' },
	});

	// 自定义配置
	let custom_options = Object.assign(
		{
			repeat_request_cancel: false, // 是否开启取消重复请求, 默认为 false
			loading: false, // 是否开启全屏loading层效果, 默认为false
			reduce_data_format: true, // 是否开启简洁的数据结构响应 减少一层data, 默认为true
			error_message_show: true, // 是否开启接口错误信息展示,默认为true
			code_message_show: false, // 是否开启code不为0时的信息提示, 默认为false
		},
		customOptions
	);

	// 添加请求拦截器
	service.interceptors.request.use(
		async (config: any) => {
			removePending(config);
			custom_options.repeat_request_cancel && addPending(config);
			// 创建loading实例
			if (custom_options.loading) {
				LoadingInstance._count++;
				if (LoadingInstance._count === 1) {
					LoadingInstance._target = ElLoading.service(loadingOptions);
				}
			}
			// 在发送请求之前做些什么 token
			if (Cookie.get('token')) {
				(<any>config.headers)['Authorization'] = `Bearer ${Cookie.get('token')}`;
			}
			return config;
		},
		async (error: AxiosError) => {
			return Promise.reject(error);
		}
	);
	// 添加响应拦截器
	service.interceptors.response.use(
		(response: AxiosResponse): any => {
			removePending(response.config);
			custom_options.loading && closeLoading(custom_options); // 关闭loading
			// 对响应数据做点什么
			if (custom_options.code_message_show && response.data && response.data.code !== 0) {
				ElMessage({
					type: 'error',
					message: response.data.message,
				});
				return Promise.reject(response.data); // code不等于0, 页面具体逻辑就不执行了
			}
			return custom_options.reduce_data_format ? response.data : response;
		},
		async (error: AxiosError): Promise<never> => {
			error.config && removePending(error.config);
			custom_options.loading && closeLoading(custom_options); // 关闭loading
			custom_options.error_message_show && httpErrorStatusHandle(error); // 处理错误状态码
			return Promise.reject(error); // 错误继续返回给到具体页面
		}
	);

	return service(axiosConfig);
}

/**
 * @description 处理异常
 * @param {*} error
 */
// 设置一个变量 处理同一时间多个错误重复弹窗口
let tokenAbnormal: boolean = false;
function httpErrorStatusHandle(error: any) {
	// 处理被取消的请求
	if (axios.isCancel(error)) return;
	let message = '';
	if (error && error.response) {
		switch (error.response.status) {
			case 302:
				message = '接口重定向了！';
				break;
			case 400:
				const { data } = error.response;
				const errStr = data.errors ? Object.values(data.errors).join(',') : '请求参数错误400！';
				message = errStr;
				break;
			case 401:
				if (!tokenAbnormal) {
					tokenAbnormal = true;
					// 弹出框
					ElMessageBox.alert('登录已过期或该账户已在其他地方登录！', '提示', {
						type: 'warning',
						showClose: false,
						closeOnClickModal: false,
						draggable: true,
					})
						.then(() => {
							Session.clear(); // 清除浏览器全部临时缓存
							Local.clear(); // 清除浏览器全部临时缓存
							Cookie.clear(); // 清除浏览器全部临时缓存
							router
								.replace(
									`/login?redirect=${router.currentRoute.value.path}&params=${JSON.stringify(
										router.currentRoute.value.query ? router.currentRoute.value.query : router.currentRoute.value.params
									)}`
								)
								.then(() => {}); // 去登录页
							location.reload(); //刷新页面
						})
						.catch((): void => {});
					// 设置定时器，确保下次异常时弹出框正常弹出
					setTimeout(() => {
						tokenAbnormal = false;
					}, 2000);
				}
				break;
			case 403:
				message = '您没有权限操作！';
				break;
			case 404:
				message = `请求地址出错: ${error.response.config.url}`;
				break; // 在正确域名下
			case 408:
				message = '请求超时！';
				break;
			case 409:
				message = '系统已存在相同数据！';
				break;
			case 500:
				if (error.response?.data.message) message = error.response.data.message;
				else message = '服务器内部错误！';
				break;
			case 501:
				message = '服务未实现！';
				break;
			case 502:
				message = '网关错误！';
				break;
			case 503:
				message = '服务不可用！';
				break;
			case 504:
				message = '服务暂时无法访问，请稍后再试！';
				break;
			case 505:
				message = 'HTTP版本不受支持！';
				break;
			default:
				message = '异常问题，请联系管理员！';
				break;
		}
	}
	if (error.message.includes('timeout')) message = '网络请求超时！';
	if (error.message.includes('Network')) message = window.navigator.onLine ? '服务端异常！' : '您断网了！';
	if (error.message.includes('Invalid URL')) message = '您请求的地址有误！';
	if (error.response?.status !== 401) {
		ElMessage({
			type: 'error',
			message,
			grouping:true
		});
	}
}

/**
 * @description 关闭Loading层实例
 * @param {*} _options
 */
function closeLoading(_options: any) {
	if (_options.loading && LoadingInstance._count > 0) LoadingInstance._count--;
	if (LoadingInstance._count === 0) {
		LoadingInstance._target.close();
		LoadingInstance._target = null;
	}
}

/**
 * @description 储存每个请求的唯一cancel回调, 以此为标识
 * @param {*} config
 */
function addPending(config: AxiosRequestConfig) {
	const pendingKey = getPendingKey(config);
	config.cancelToken =
		config.cancelToken ||
		new axios.CancelToken((cancel) => {
			if (!pendingMap.has(pendingKey)) {
				pendingMap.set(pendingKey, cancel);
			}
		});
}

/**
 * @description 删除重复的请求
 * @param {*} config
 */
function removePending(config: AxiosRequestConfig) {
	const pendingKey = getPendingKey(config);
	if (pendingMap.has(pendingKey)) {
		const cancelToken = pendingMap.get(pendingKey);
		// 如你不明白此处为什么需要传递pendingKey可以看文章下方的补丁解释
		cancelToken(pendingKey);
		pendingMap.delete(pendingKey);
	}
}

/**
 * @description 生成唯一的每个请求的唯一key
 * @param {*} config
 * @returns
 */
function getPendingKey(config: AxiosRequestConfig) {
	let { url, method, params, data } = config;
	// if (typeof data === 'string') data = JSON.parse(data); // response里面返回的config.data是个字符串对象
	return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&');
}
