import axios, { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus';
import type { MessageHandler } from 'element-plus';
import { Session } from '/@/utils/storage';
import qs from 'qs';
import { getChangedFields } from '/@/utils/diffUtils';
import { handleModuleNotEnabled } from '/@/utils/assetSubscribe';

/**
 * 控制一次请求的错误提示归属（默认 global）：
 * - global: 由拦截器统一弹出后端返回的 message（含 HTTP 与业务 JSON）
 * - page: 不自动弹窗，仅 reject；请在页面 catch 内自行处理（应与全局择一，避免重复）
 * - silent: 完全静默（轮询等）
 */
export interface RequestOptions {
	errorMode?: 'global' | 'page' | 'silent';
}

declare module 'axios' {
	interface AxiosRequestConfig {
		requestOptions?: RequestOptions;
	}

	interface InternalAxiosRequestConfig {
		requestOptions?: RequestOptions;
	}
}

// 标记是否正在处理 token 过期，避免出现多个登录过期弹窗。
let isHandlingTokenExpired = false;

// 始终只保留一个错误消息实例，新的错误会先关闭旧的提示。
let activeErrorMessage: MessageHandler | null = null;

// 同类错误提示做一个短时间节流，避免接口连发时刷屏。
let lastErrorTime = 0;
const ERROR_MESSAGE_INTERVAL = 2000;

const getErrorMode = (config?: InternalAxiosRequestConfig) => config?.requestOptions?.errorMode ?? 'global';
const shouldShowGlobalError = (config?: InternalAxiosRequestConfig) => getErrorMode(config) === 'global';

/**
 * 从接口响应体解析可读错误文案（JSON API、Spring 风格等）
 */
export function extractBackendMessage(data: unknown): string | undefined {
	if (data == null) return undefined;
	if (typeof data === 'string') {
		const t = data.trim();
		return t.length > 0 && t.length < 2000 ? t : undefined;
	}
	if (typeof data !== 'object') return undefined;
	const o = data as Record<string, unknown>;
	const pick = (v: unknown) => (typeof v === 'string' && v.trim() ? v.trim() : undefined);
	return (
		pick(o.message) ||
		pick(o.msg) ||
		pick(o.error) ||
		(typeof o.detail === 'string' ? pick(o.detail) : undefined)
	);
}

const closeActiveErrorMessage = () => {
	activeErrorMessage?.close();
	activeErrorMessage = null;
};

/**
 * token 过期只接受明确的后端信号：
 * - HTTP 401
 * - 业务 code = 401
 * - 已知的固定错误文案
 * 不再使用模糊的 includes('token')，避免把普通业务错误误判成登录过期。
 */
const isTokenExpiredError = (httpStatus?: number, code?: number, message?: string) => {
	const normalizedMessage = String(message || '')
		.trim()
		.toLowerCase();
	const tokenExpiredMessages = ['token is invalid', 'token 解析失败', 'decrypt error', 'jwt expired', 'invalid token'];

	return httpStatus === 401 || code === 401 || tokenExpiredMessages.includes(normalizedMessage);
};

/**
 * 全局错误提示统一从这里走：
 * 1. 先判断当前请求是否允许全局弹错
 * 2. 再做节流，防止短时间重复提示
 * 3. 最后保证页面上同一时刻只有一个错误弹窗
 */
const showErrorMessage = (message: string, config?: InternalAxiosRequestConfig) => {
	if (!shouldShowGlobalError(config)) return;

	const now = Date.now();
	if (now - lastErrorTime < ERROR_MESSAGE_INTERVAL) return;

	lastErrorTime = now;
	closeActiveErrorMessage();

	let currentMessage: MessageHandler | null = null;
	currentMessage = ElMessage.error({
		message,
		onClose: () => {
			if (activeErrorMessage === currentMessage) {
				activeErrorMessage = null;
			}
		},
	});
	activeErrorMessage = currentMessage;
};

const service: AxiosInstance = axios.create({
	baseURL: import.meta.env.VITE_API_URL,
	timeout: 50000,
	headers: { 'Content-Type': 'application/json' },
	paramsSerializer: {
		serialize(params) {
			return qs.stringify(params, { allowDots: true, arrayFormat: 'brackets' });
		},
	},
});

/**
 * 登录过期时优先关闭普通错误消息，再弹出唯一的登录过期确认框。
 * 这样用户不会先看到一个普通报错，再叠一个登录过期弹窗。
 */
const handleTokenExpired = () => {
	if (isHandlingTokenExpired) return;

	isHandlingTokenExpired = true;
	closeActiveErrorMessage();

	ElMessageBox.alert('登录状态已过期，请重新登录', '提示', {
		confirmButtonText: '确定',
		showClose: false,
		closeOnClickModal: false,
		closeOnPressEscape: false,
	})
		.then(() => {
			performLogout();
		})
		.catch(() => {
			performLogout();
		});
};

/**
 * 统一退出动作：
 * - 只清理登录态相关缓存
 * - 重置 token 过期处理标记
 * - 最后回到登录页
 */
const performLogout = () => {
	Session.clearAuth();
	isHandlingTokenExpired = false;
	setTimeout(() => {
		window.location.href = '/#/login';
	}, 500);
};

const requestInterceptor = (config: InternalAxiosRequestConfig) => {
	const token = Session.get('token');
	if (token) {
		config.headers!['Authorization'] = `Bearer ${token}`;
	}

	// PUT 请求只传变更字段，避免把未修改的数据整包提交给后端。
	if (config.method?.toLowerCase() === 'put' && config.data && typeof config.data === 'object') {
		const { _originalData, ...currentData } = config.data;

		if (_originalData && typeof _originalData === 'object') {
			const idField = currentData.id || currentData.Id || currentData.ID;
			const changedFields = getChangedFields(_originalData, currentData, {
				exclude: ['_originalData', 'id', 'Id', 'ID'],
			});

			config.data = Object.keys(changedFields).length > 0 ? { id: idField, ...changedFields } : { id: idField };
		}
	}

	return config;
};

const requestErrorHandler = (error: any) => Promise.reject(error);

const responseInterceptor = (response: AxiosResponse) => {
	// 文件流直接返回原始响应，调用方需要自行处理 Blob。
	if (
		response.config.responseType === 'blob' ||
		response.headers['content-type']?.includes('application/zip') ||
		response.headers['content-type']?.includes('application/octet-stream')
	) {
		return response;
	}

	const res = response.data;
	const httpStatus = response.status;
	const code = res?.code;
	const message = extractBackendMessage(res);
	const config = response.config;

	if (isTokenExpiredError(httpStatus, code, message)) {
		handleTokenExpired();
		return Promise.reject(new Error('登录状态已过期'));
	}

	const requestUrl = response.config.url || '';
	if (code === 402 && !requestUrl.includes('/assets/asset/sku/')) {
		const currentPath = window.location.hash.replace('#', '') || window.location.pathname;
		handleModuleNotEnabled(currentPath);
		return Promise.reject(new Error('模块未开通'));
	}

	// 定义已知的正常 code
	const knownSuccessCodes = [0, 200];
	// 定义已知的业务错误 code（这些 code 会显示后端返回的 message）
	const knownErrorCodes = [401, 402, 403, 404, 429, 500, 502, 503];

	// 业务成功，直接返回
	if (code !== undefined && knownSuccessCodes.includes(code)) {
		return res;
	}

	// 业务失败处理
	if (code !== undefined && !knownSuccessCodes.includes(code)) {
		let errorMsg: string;

		// 已知的业务错误 code，使用后端返回的 message
		if (knownErrorCodes.includes(code)) {
			errorMsg = message || `请求失败(${code})`;
		} else {
			// 未知的 code：优先使用后端 message，便于排查业务含义
			errorMsg = message || '后端异常，请联系管理员';
		}

		showErrorMessage(errorMsg, config);
		return Promise.reject(new Error(errorMsg));
	}

	return res;
};

const responseErrorHandler = (error: any) => {
	const config = error.config as InternalAxiosRequestConfig | undefined;
	const httpStatus = error.response?.status;
	const responseData = error.response?.data;
	const responseMessage = extractBackendMessage(responseData);

	if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) {
		showErrorMessage('请求超时，请检查网络连接', config);
		return Promise.reject(new Error('请求超时'));
	}

	if (!error.response) {
		return Promise.reject(error);
	}

	if (isTokenExpiredError(httpStatus, error.response?.data?.code as number | undefined, responseMessage)) {
		handleTokenExpired();
		return Promise.reject(new Error('登录状态已过期'));
	}

	const requestUrl = error.response.config?.url || '';

	switch (httpStatus) {
		case 402:
			if (!requestUrl.includes('/assets/asset/sku/') && !requestUrl.includes('getAssetAndSku')) {
				const lastSubscribeTime = sessionStorage.getItem('lastSubscribeTime');
				const now = Date.now();
				if (lastSubscribeTime && now - parseInt(lastSubscribeTime) < 5000) {
					showErrorMessage(responseMessage ?? '服务开通中，请稍后刷新页面', config);
					return Promise.reject(new Error('模块开通中'));
				}

				const currentPath = window.location.hash.replace('#', '') || window.location.pathname;
				handleModuleNotEnabled(currentPath);
				return Promise.reject(new Error('模块未开通'));
			}
			showErrorMessage(responseMessage ?? '服务未开通', config);
			break;
		case 403:
			showErrorMessage(responseMessage ?? '没有权限访问该资源', config);
			break;
		case 404:
			showErrorMessage(responseMessage ?? '请求的资源不存在', config);
			break;
		case 429:
			// 429 是限流，不等于登录过期，这里只保留频率提示。
			showErrorMessage(responseMessage ?? '请求过于频繁，请稍后再试', config);
			break;
		case 500:
			showErrorMessage(responseMessage ?? '服务器内部错误', config);
			break;
		case 502:
			showErrorMessage(responseMessage ?? '网关错误', config);
			break;
		case 503:
			showErrorMessage(responseMessage ?? '服务不可用', config);
			break;
		default:
			if (httpStatus >= 400) {
				showErrorMessage(responseMessage ?? `请求失败(${httpStatus})`, config);
			}
	}

	return Promise.reject(error);
};

service.interceptors.request.use(requestInterceptor, requestErrorHandler);
service.interceptors.response.use(responseInterceptor, responseErrorHandler);

/**
 * 从 axios / 业务 reject 中取出后端返回的提示文案（与全局拦截器同源逻辑；silent / 特殊场景下可在页面使用）。
 */
export function getApiErrorMessage(error: unknown, fallback = '操作失败'): string {
	const e = error as any;
	const fromBody = extractBackendMessage(e?.response?.data);
	if (fromBody != null && fromBody !== '') {
		return fromBody;
	}
	const msg = e?.message;
	if (typeof msg === 'string' && msg.trim() !== '') {
		if (/^Request failed with status code \d+$/i.test(msg)) {
			return extractBackendMessage(e?.response?.data) ?? fallback;
		}
		if (msg === 'Network Error') {
			return '网络异常，请检查网络连接';
		}
		return msg;
	}
	return fallback;
}

export default service;
export { closeActiveErrorMessage, showErrorMessage };
