import { AkReqUploadOptions, AkReqOptions, AkReqResponse, AkReqError } from './interface.uts';
import { SUPA_URL, SUPA_KEY, IS_TEST_MODE } from '@/ak/config.uts';

// token 持久化 key
const ACCESS_TOKEN_KEY = 'akreq_access_token';
const REFRESH_TOKEN_KEY = 'akreq_refresh_token';
const EXPIRES_AT_KEY = 'akreq_expires_at';

// 优化：用静态变量缓存 token，只有 set/clear 时同步 storage

// Web 端(H5)由于多账号联调需要，开启单 Tab 隔离的储流（sessionStorage）
function setAuthStore(key : string, value : any) {
    // #ifdef H5
    sessionStorage.setItem(key, String(value));
    return;
    // #endif
    uni.setStorageSync(key, value);
}
function getAuthStore(key : string) : any | null {
    // #ifdef H5
    return sessionStorage.getItem(key);
    // #endif
    return uni.getStorageSync(key);
}
function removeAuthStore(key : string) {
    // #ifdef H5
    sessionStorage.removeItem(key);
    return;
    // #endif
    uni.removeStorageSync(key);
}

/**
 * 安全读取 UTSJSONObject 或普通 JS 对象的字段。
 * 微信小程序编译后 res.header / res.data / options.headers 均为普通 JS 对象，
 * 直接调用 .getString() / .set() 会抛 "not a function" 异常。
 * 此函数统一处理两种情况：先尝试 getString，不可用则 fallback 到 []。
 */
function safeStr(obj: any, key: string): string | null {
    if (obj == null) return null
    try {
        if (typeof obj.getString === 'function') {
            const v = obj.getString(key)
            return v != null ? String(v) : null
        }
    } catch (e) {}
    const v = obj[key]
    return (v != null && v !== '') ? String(v) : null
}

let _accessToken : string | null = null;
let _refreshToken : string | null = null;
let _expiresAt : number | null = null;

export class AkReq {
	static setToken(token : string, refreshToken : string, expiresAt : number) {
		_accessToken = token;
		_refreshToken = refreshToken;
		_expiresAt = expiresAt;
		setAuthStore(ACCESS_TOKEN_KEY, token);
		setAuthStore(REFRESH_TOKEN_KEY, refreshToken);
		setAuthStore(EXPIRES_AT_KEY, expiresAt);
	}
	static getToken() : string | null {
		if (_accessToken != null) return _accessToken;
		const t = getAuthStore(ACCESS_TOKEN_KEY) as string | null;
		_accessToken = t;
		return t;
	}
	static getRefreshToken() : string | null {
		if (_refreshToken != null) return _refreshToken;
		const t = getAuthStore(REFRESH_TOKEN_KEY) as string | null;
		_refreshToken = t;
		return t;
	} static getExpiresAt() : number | null {
		const val = _expiresAt;
		if (val != null) return val;
		const t = getAuthStore(EXPIRES_AT_KEY) as number | null;
		_expiresAt = t;
		return t;
	}
	static clearToken() {
		_accessToken = null;
		_refreshToken = null;
		_expiresAt = null;
		removeAuthStore(ACCESS_TOKEN_KEY);
		removeAuthStore(REFRESH_TOKEN_KEY);
		removeAuthStore(EXPIRES_AT_KEY);
	}	// 判断 token 是否即将过期（提前5分钟刷新）
	static isTokenExpiring() : boolean {
		const expiresAt = this.getExpiresAt();
		if (expiresAt === null || expiresAt == 0) {
			return true;
		}
		const now = Math.floor(Date.now() / 1000);
		return (expiresAt - now) < 300; // 提前5分钟刷新
	}

	// 自动刷新 token，返回 true=已刷新，false=未刷新
	static async refreshTokenIfNeeded(apikey ?: string) : Promise<boolean> {
		// 没有 access_token 直接返回，不刷新
		const accessToken = this.getToken();
		if (accessToken === null || accessToken === "") {
			return false;
		}
		if (!this.isTokenExpiring()) {
			return false;
		}
		const refreshToken = this.getRefreshToken();
		if (refreshToken === null || refreshToken === "") {
			this.clearToken();
			return false;
		}
		// 构造 header，必须带 apikey
		let headers = new UTSJSONObject();
		if (apikey !== null && apikey !== "") {
			headers.set('apikey', apikey)
		}
		const reqData = new UTSJSONObject()
		reqData.set('refresh_token', refreshToken)
		try {
			const res = await this.request({
				url: SUPA_URL + '/auth/v1/token?grant_type=refresh_token',
				method: 'POST',
				data: reqData,
				headers: headers,
				contentType: 'application/json'
			}, true); // skipRefresh=true，避免递归
			const data = res.data as UTSJSONObject | null;
			let accessToken : string | null = null;
			let refreshTokenNew : string | null = null;
			let expiresAt : number | null = null;
			if (data != null && typeof data.getString === 'function' && typeof data.getNumber === 'function') {
				accessToken = data.getString('access_token');
				refreshTokenNew = data.getString('refresh_token');
				expiresAt = data.getNumber('expires_at');
			}
			if (accessToken !== null && refreshTokenNew !== null && expiresAt !== null) {
				this.setToken(accessToken, refreshTokenNew, expiresAt);
				return true;
			} else { this.clearToken(); uni.$emit('AUTH_SESSION_EXPIRED', { reason: 'refresh_failed' }); return false; }
		} catch (e) {
			this.clearToken();
			return false;
		}
	}
	// options: AkReqOptions, skipRefresh: boolean = false
	static async request(options : AkReqOptions, skipRefresh ?: boolean) : Promise<AkReqResponse<any>> {
		// 自动刷新 token
		if (skipRefresh != true) {
			// safeStr 兼容 plain object 和 UTSJSONObject
			const apikey = safeStr(options.headers as any, 'apikey');
			await this.refreshTokenIfNeeded(apikey);
		}

		// 构建请求 headers：用 safeStr 兼容 plain object / UTSJSONObject
		// aksupa.uts 创建的 headers 是“{ key: val } as UTSJSONObject” ，只是类型标注，运行时是 plain object
		const origHdr = options.headers as any
		const origApikey    = safeStr(origHdr, 'apikey')
		const origCT        = safeStr(origHdr, 'Content-Type') ?? safeStr(origHdr, 'content-type')
		const origPrefer    = safeStr(origHdr, 'Prefer')
		const origAuth      = safeStr(origHdr, 'Authorization') ?? safeStr(origHdr, 'authorization')
		const origRange     = safeStr(origHdr, 'Range')
		const origRangeUnit = safeStr(origHdr, 'Range-Unit')

		// apikey：优先用传入的，再 fallback 到全局 SUPA_KEY
		const finalApikey = origApikey ?? (SUPA_KEY !== '' ? SUPA_KEY : null)

		// Authorization：登录态使用用户 access_token。
		// 匿名态仅发送 apikey，避免自托管 Kong/PostgREST 将 anon key 当作 Bearer JWT 校验，触发 401/PGRST301。
		const token = this.getToken()
		let finalAuth: string | null = origAuth
		let authMode: string = 'none'
		if (finalAuth == null) {
			if (token != null && token !== '') {
				// 登录态：用用户 access_token
				finalAuth = `Bearer ${token}`
				authMode = 'user-token'
			} else if (finalApikey != null && finalApikey !== '') {
				// 匿名态：只发送 apikey，不补 Authorization
				finalAuth = null
				authMode = 'apikey-only'
			}
		} else {
			authMode = 'pre-set'
		}

		// 组装最终 header（plain object，所有平台均可正确传递）
		const currentHeaders: Record<string, string> = {}
		if (finalApikey != null && finalApikey !== '') currentHeaders['apikey'] = finalApikey
		if (finalAuth   != null && finalAuth   !== '') currentHeaders['Authorization'] = finalAuth
		currentHeaders['Content-Type'] = origCT ?? options.contentType ?? 'application/json'
		currentHeaders['Accept']       = 'application/json'
		if (origPrefer    != null) currentHeaders['Prefer']     = origPrefer
		if (origRange     != null) currentHeaders['Range']      = origRange
		if (origRangeUnit != null) currentHeaders['Range-Unit'] = origRangeUnit

		// 请求发出前诊断日志（脱敏：前6位+后4位）
		const dbgKey = finalApikey != null && finalApikey.length > 14
			? finalApikey.substring(0, 6) + '...' + finalApikey.substring(finalApikey.length - 4)
			: (finalApikey != null ? '(short)' : '(MISSING!)')
		const dbgAuth = finalAuth != null && finalAuth.length > 15
			? finalAuth.substring(0, 13) + '...' + finalAuth.substring(finalAuth.length - 4)
			: (finalAuth != null ? finalAuth : '(MISSING!)')
		console.log('[ak-req]', (options.method ?? 'GET'), options.url)
		console.log('[ak-req] apikey:', dbgKey, '| Authorization:', dbgAuth, '| auth-mode:', authMode, '| prefer:', origPrefer ?? '(none)')

		const timeout = options.timeout ?? 10000;
		const maxRetry = Math.max(0, options.retryCount ?? 0);
		const baseDelay = Math.max(0, options.retryDelayMs ?? 300);

		const doOnce = (): Promise<AkReqResponse<any>> => {
			return new Promise<AkReqResponse<any>>((resolve) => {
				uni.request({
					url: options.url,
					method: options.method ?? 'GET',
					data: options.data,
					header: currentHeaders,
					timeout: timeout,
					success: (res) => {
						// HEAD 请求特殊处理：没有响应体，只有 headers
						if (options.method == 'HEAD') {
							const result = AkReq.createResponse<any>(
								res.statusCode,
								[] as Array<any>,
								res.header as UTSJSONObject
							);
							resolve(result);
							return;
						}

						// 兼容 res.data 可能为 string 或 UTSJSONObject 或 UTSArray
						// 先读取响应 content-type，判断是否应该 JSON.parse
						// res.header 在 MP 环境下是普通 JS 对象（无 getString），用括号访问
						let respContentType = ''
						try {
							const hdr = res.header as any
							if (hdr != null) {
								respContentType = hdr['content-type'] ?? hdr['Content-Type'] ?? ''
							}
						} catch(e) {}

						let data : UTSJSONObject | Array<UTSJSONObject> | null;
						if (typeof res.data == 'string') {
							const strData = res.data as string;
							if (strData.length > 0 && /[^\s]/.test(strData)) {
								const looksLikeHtml = strData.trimStart().startsWith('<')
								if (looksLikeHtml || (respContentType.indexOf('text/html') >= 0)) {
									// 明确是 HTML，直接跳过 JSON.parse
									const preview = strData.substring(0, 250)
									console.error('[ak-req] non-json response (HTML)')
									console.error('[ak-req] status:', res.statusCode)
									console.error('[ak-req] content-type:', respContentType)
									console.error('[ak-req] raw preview:', preview)
									data = new UTSJSONObject({ raw: strData })
								} else {
									try {
										data = JSON.parse(strData) as UTSJSONObject;
									} catch (e) {
										// 非 JSON 响应（纯文本/数字等）
										const preview = strData.substring(0, 250)
										console.error('[ak-req] json parse failed')
										console.error('[ak-req] status:', res.statusCode)
										console.error('[ak-req] content-type:', respContentType)
										console.error('[ak-req] raw preview:', preview)
										data = new UTSJSONObject({ raw: strData });
									}
								}
							} else {
								data = null;
							}
						} else if (Array.isArray(res.data)) {
							data = res.data as UTSJSONObject[];
						} else {
							// MP 下 res.data 可能是 plain JS object（平台已预解析 JSON），用 safeStr 兼容
							const objData = res.data as any;
							data = objData as UTSJSONObject;
							if (objData != null) {
								const accessToken     = safeStr(objData, 'access_token')
								const refreshTokenNew = safeStr(objData, 'refresh_token')
								const expiresAtRaw    = objData['expires_at']
								const expiresAt       = expiresAtRaw != null ? Number(expiresAtRaw) : null
								if (accessToken !== null && refreshTokenNew !== null && expiresAt !== null) {
									AkReq.setToken(accessToken, refreshTokenNew, expiresAt);
								}
							}
						}
						const result = AkReq.createResponse<any>(
							res.statusCode,
							data ?? {},
							res.header as UTSJSONObject
						);
						if (res.statusCode >= 400) {
							let bodyPreview = ''
							try {
								if (typeof res.data == 'string') {
									bodyPreview = (res.data as string).substring(0, 500)
								} else {
									bodyPreview = JSON.stringify(res.data).substring(0, 500)
								}
							} catch (e) {
								bodyPreview = '[unserializable response body]'
							}
							console.error('[ak-req] HTTP error response')
							console.error('[ak-req] status:', res.statusCode)
							console.error('[ak-req] url:', options.url)
							console.error('[ak-req] body:', bodyPreview)
						}
						resolve(result);
					},
					fail: (err) => {
						const errStatus = (err.errCode != null && typeof err.errCode === 'number') ? err.errCode : 0;
						const result = AkReq.createResponse<any>(
							errStatus,
							{} as UTSJSONObject,
							{} as UTSJSONObject,
							new UniError('uni-request', errStatus, err.errMsg ?? 'request fail')
						);
						resolve(result);
					}
				});
			});
		};

		let attempt = 0;
		let lastRes: AkReqResponse<any> | null = null;
		while (attempt <= maxRetry) {
			const res = await doOnce();
			lastRes = res;
			// 仅网络失败/超时（errCode 非 0 且 status 非 2xx/3xx）时重试
			const status = res.status ?? 0;
			const isOk = status >= 200 && status < 400;
			if (isOk) return res;
			if (attempt === maxRetry) break;
			// 简单退避
			const delay = baseDelay * Math.pow(2, attempt);
    await new Promise<void>((r) => { setTimeout(() => { r(); }, delay); });
			attempt++;
		}
		const finalRes = lastRes!!;
		// 全局处理 401 未授权
		if ((finalRes.status === 401) && (skipRefresh !== true)) {
			// 401 诊断日志：区分"前端 header 问题"vs"服务端 key/实例问题"
			const sentApikey  = (currentHeaders as any)['apikey']
			const sentAuth    = (currentHeaders as any)['Authorization']
			const dbgSentKey  = sentApikey != null && sentApikey.length > 14
				? sentApikey.substring(0, 6) + '...' + sentApikey.substring(sentApikey.length - 4)
				: (sentApikey != null ? '(short)' : '(MISSING!)')
			const dbgSentAuth = sentAuth != null && sentAuth.length > 15
				? sentAuth.substring(0, 13) + '...' + sentAuth.substring(sentAuth.length - 4)
				: (sentAuth != null ? '(short)' : '(MISSING!)')
			console.error('[ak-req] ★ 401 Unauthorized')
			console.error('[ak-req] url:', options.url)
			console.error('[ak-req] auth-mode: ' + authMode)
			console.error('[ak-req] 发送 apikey:', dbgSentKey)
			console.error('[ak-req] 发送 Authorization:', dbgSentAuth)
			console.error('[ak-req] response body:', JSON.stringify(finalRes.data))
			// 前端 header 检查
			if (sentApikey == null || sentApikey === '') {
				console.error('[ak-req] ✗ 前端问题：apikey 未发送，检查 SUPA_KEY 是否已配置')
			} else if ((sentAuth == null || sentAuth === '') && authMode == 'apikey-only') {
				console.error('[ak-req] ℹ 当前请求按 apikey-only 模式发送，这是匿名登录/匿名接口的预期行为')
				console.error('[ak-req] ✗ 401 更可能来自服务端网关认证：请检查 SUPA_URL 对应实例与 SUPA_KEY/ANON_KEY 是否匹配')
			} else if (sentAuth == null || sentAuth === '') {
				console.error('[ak-req] ✗ 前端问题：Authorization 未发送，请检查当前请求是否应携带用户 token 或显式 Authorization')
			} else {
				console.error('[ak-req] ✓ 前端 header 已正确发送，401 来自服务端')
				console.error('[ak-req] 请运维核查以下服务端配置：')
				console.error('[ak-req]   1. SUPA_KEY 是否属于 9126 这个实例（不同实例 key 不通用）')
				console.error('[ak-req]   2. 9126 和 9127 是否为同一套 docker-compose / 同一实例')
				console.error('[ak-req]   3. Kong 是否已 reload 到最新 consumer/key 配置')
				console.error('[ak-req]   4. Supabase 实例的 ANON_KEY 环境变量与 SUPA_KEY 是否一致')
				console.error('[ak-req]   5. categories/ml_brands/ml_member_levels 是否在该实例 public schema 下')
				console.error('[ak-req]   6. RLS policy 是否允许 anon role SELECT')
			}

			uni.$emit('AUTH_SESSION_EXPIRED', { reason: '401' });
			try {
				this.clearToken();
				// IS_TEST_MODE=true 时不 showToast 和跳转，避免干扰调试
				if (IS_TEST_MODE !== true) {
					uni.showToast({ title: '未授权或登录已过期，请重新登录', icon: 'none' });
				}
			} catch (e) {}
		}
		return finalRes;
	}

	// 新增 upload 方法，支持 uni.uploadFile，自动带 token/apikey	
	static async upload(options : AkReqUploadOptions) : Promise<AkReqResponse<any>> {
		// 上传前尝试刷新 token（若即将过期）。优先从 options.headers 或 apikey 字段获取 apikey
		let apikey: string | null = null;
		const hdr = options.headers;
		if (hdr != null && typeof hdr.getString === 'function') {
			apikey = hdr.getString('apikey');
		}
    if (apikey == null && options.apikey != null) apikey = options.apikey;
    await this.refreshTokenIfNeeded(apikey != null ? apikey : null);

		let headers = options.headers ?? ({} as UTSJSONObject);
		const token = this.getToken();
		if (token != null && token !== "") {
			headers = Object.assign({}, headers, { Authorization: `Bearer ${token}` }) as UTSJSONObject;
		}
    if (apikey != null && apikey !== "") {
			headers = Object.assign({}, headers, { apikey: apikey }) as UTSJSONObject;
		}
		// 默认 Accept
		headers = Object.assign({ Accept: 'application/json' } as UTSJSONObject, headers) as UTSJSONObject;

		const timeout = options.timeout ?? 10000;
		const maxRetry = Math.max(0, options.retryCount ?? 0);
		const baseDelay = Math.max(0, options.retryDelayMs ?? 300);

		const doOnce = (): Promise<AkReqResponse<any>> => {
			return new Promise<AkReqResponse<any>>((resolve) => {
		const task = uni.uploadFile({
			url: options.url,
			filePath: options.filePath,
			name: options.name,
			formData: options.formData ?? {},
			header: headers,
			timeout: timeout,
			success: (res : UploadFileSuccess) => {
				let parsed: UTSJSONObject | null = null;
				try {
					parsed = JSON.parse(res.data) as UTSJSONObject;
				} catch (e) {
					parsed = null;
				}
				if (parsed != null) {
					const accessToken = parsed.getString('access_token');
					const refreshTokenNew = parsed.getString('refresh_token');
					const expiresAt = parsed.getNumber('expires_at');
					if (accessToken !== null && refreshTokenNew !== null && expiresAt !== null) {
						AkReq.setToken(accessToken, refreshTokenNew, expiresAt);
					}
				}
				const result = AkReq.createResponse<any>(
					res.statusCode,
					parsed ?? {},
					headers
				);
				resolve(result);
			},
			fail: (err) => {
				const errStatus = (err.errCode != null && typeof err.errCode === 'number') ? err.errCode : 0;
				const result = AkReq.createResponse<any>(
					errStatus,
					{} as UTSJSONObject,
					{} as UTSJSONObject,
					new UniError('uni-upload', errStatus, err.errMsg ?? 'upload fail')
				);
				resolve(result);
			}
		});
		if (options.onProgress != null && task != null) {
			const progressCallback = (res: OnProgressUpdateResult) => {
				const percent = res.progress as number; // 0-100
				const sent = res.totalBytesSent as number | null;
				const expected = res.totalBytesExpectedToSend as number | null;
				if (options.onProgress != null) {
					options.onProgress(percent, sent, expected);
				}
			};
			task.onProgressUpdate(progressCallback);
		}
			});
		};

		let attempt = 0;
		let lastRes: AkReqResponse<any> | null = null;
		while (attempt <= maxRetry) {
			const res = await doOnce();
			lastRes = res;
			const status = res.status ?? 0;
			const isOk = status >= 200 && status < 400;
			if (isOk) return res;
			if (attempt === maxRetry) break;
			const delay = baseDelay * Math.pow(2, attempt);
			await new Promise<void>((resolve) => {
				setTimeout(() => {
					resolve();
				}, delay);
			});
			attempt++;
		}
		return lastRes!!;
	}
	// 辅助方法：创建 AkReqResponse 对象，避免类型推断问题
	static createResponse<T>(
		status: number,
		data: T | Array<T> ,
		headers: UTSJSONObject,
		error: UniError | null = null,
		total: number | null = null,
		page: number | null = null,
		limit: number | null = null,
		hasmore: boolean | null = null,
		origin: any | null = null
	): AkReqResponse<T> {
		return {
			status,
			data,
			headers,
			error,
			total,
			page,
			limit,
			hasmore,
			origin
		};
	}

}

export default AkReq;