// ============================================================
// auth-token — Token 生命周期管理（存取 refresh_token + 刷新回调 + 主动续期）
//
// 职责：
//   - 持久化 / 清除 refresh_token（通过 safeStorage 加密）
//   - 向 request.ts 注册 handle401 触发的 token 刷新回调
//   - 主动刷新定时器：在 access_token 过期前自动续期，避免被动 401
//   - request.ts 不依赖此模块（通过 setTokenRefreshHandler 解耦）
//
// 刷新失败策略（关键设计）：
//   - 永久失败（refresh_token 已过期/无效）→ THROW → handle401 清 token + 弹登录
//   - 临时失败（网络超时/无连接）      → return null → handle401 仅拒绝当前请求，不登出
//
// 安全：
//   - refresh_token 由 Electron safeStorage 加密后存入 localStorage
//   - 内存缓存供同步读取（setTokenRefreshHandler 回调内使用）
// ============================================================

import { setToken, setTokenRefreshHandler, RequestError, RequestErrorType } from '@/shared/services/http';
import { AuthAPI, type RefreshTokenResponseBody } from './auth-api';
import { isRefreshError } from '@/shared/services/types';
import {
   getSafeStore,
   setSafeStore,
   clearSafeStore,
   initSafeStore,
} from '@/shared/utils/safe-storage';
import { logger } from '@/shared/utils/bus';

const REFRESH_KEY = 'ele-heixiu-refresh-token';

// ============================================================
// 自定义错误：refresh_token 永久失效
// ============================================================

/**
 * 当 refresh_token 已过期或被服务端拒绝时抛出。
 * handle401 据此区分"需要重新登录"和"网络暂时不通"。
 */
export class RefreshTokenExpiredError extends Error {
   constructor(message = '登录已过期，请重新登录') {
      super(message);
      this.name = 'RefreshTokenExpiredError';
   }
}

// ============================================================
// 初始化
// ============================================================

/**
 * 启动时初始化 refresh_token：从加密存储解密到内存缓存。
 * 应在 AppProvider 挂载时调用。
 */
export async function initRefreshTokenStore(): Promise<void> {
   await initSafeStore(REFRESH_KEY);
}

// ============================================================
// 存取
// ============================================================

/** 获取当前内存缓存中的 refresh_token（同步，需先调用 initRefreshTokenStore） */
export function getStoredRefreshToken(): string | null {
   return getSafeStore(REFRESH_KEY);
}

/** 设置 refresh_token（异步加密存储 + 更新内存缓存） */
export async function setStoredRefreshToken(token: string): Promise<void> {
   await setSafeStore(REFRESH_KEY, token);
}

/** 清除 refresh_token（同步清除内存缓存 + localStorage） */
export function clearStoredRefreshToken(): void {
   clearSafeStore(REFRESH_KEY);
}

// ============================================================
// JWT 解码（用于主动刷新定时器）
// ============================================================

/** 从 JWT access_token 中提取过期时间（毫秒时间戳），解码失败返回 0 */
function getTokenExpiryMs(token: string): number {
   try {
      const payload = JSON.parse(atob(token.split('.')[1]));
      return (payload.exp as number) * 1000;
   } catch {
      return 0;
   }
}

// ============================================================
// 主动刷新定时器
// ============================================================

/** 主动刷新定时器句柄（模块级单例） */
let refreshTimer: ReturnType<typeof setTimeout> | null = null;

/**
 * 安排在 access_token 过期前自动刷新。
 * 在 75% 过期时间点触发，确保在被动 401 之前完成续期。
 *
 * @param accessToken - 当前有效的 access_token（JWT 格式）
 */
export function scheduleProactiveRefresh(accessToken: string): void {
   // 先清除旧定时器
   clearProactiveRefresh();

   const expiryMs = getTokenExpiryMs(accessToken);
   if (!expiryMs) return;

   const remainingMs = expiryMs - Date.now();
   if (remainingMs <= 0) return; // 已经过期，等被动刷新

   // 在 75% 过期时间点触发
   const delayMs = Math.max(60_000, remainingMs * 0.75); // 至少 60s 后再刷新

   refreshTimer = setTimeout(async () => {
      refreshTimer = null;
      try {
         const refreshToken = getStoredRefreshToken();
         if (!refreshToken) return;

         const res: RefreshTokenResponseBody = await AuthAPI.refreshToken({ refresh_token: refreshToken });
         await setToken(res.access_token);
         await setStoredRefreshToken(res.refresh_token);

         // 成功 → 安排下一次刷新
         scheduleProactiveRefresh(res.access_token);
      } catch {
         // 主动刷新失败 → 不弹登录，等请求 401 时走被动刷新
      }
   }, delayMs);
}

/** 清除主动刷新定时器（退出登录时调用） */
export function clearProactiveRefresh(): void {
   if (refreshTimer !== null) {
      clearTimeout(refreshTimer);
      refreshTimer = null;
   }
}

// ============================================================
// 向 request.ts 注册 401 刷新回调（被动刷新）
// ============================================================

/**
 * 向 request.ts 注册 token 刷新回调。
 *
 * 返回约定（由 handle401 解释）：
 *   - 成功返回新 access_token  → handle401 重试原请求
 *   - return null              → 临时失败（网络问题），handle401 拒绝当前请求但不登出
 *   - throw RefreshTokenExpiredError → 永久失败，handle401 清 token + 弹登录
 *
 * 应在 AppProvider 挂载时调用一次。
 */
export function initAuthRefresh(): void {
   setTokenRefreshHandler(async (): Promise<string | null> => {
      const refreshToken = getStoredRefreshToken();
      if (!refreshToken) {
         throw new RefreshTokenExpiredError();
      }

      try {
         const res: RefreshTokenResponseBody = await AuthAPI.refreshToken({ refresh_token: refreshToken });

         // 更新持久化 token（加密存储）
         await setToken(res.access_token);
         await setStoredRefreshToken(res.refresh_token);

         // 主动刷新：为新 token 安排下次续期
         scheduleProactiveRefresh(res.access_token);

         return res.access_token;
      } catch (err) {
         // ---- 永久失败：refresh_token 已过期/无效 ----
         if (isRefreshError(err)) {
            logger.warn('auth', 'Token 刷新永久失败（refresh_token 已过期）', err instanceof Error ? err : undefined);
            clearStoredRefreshToken();
            clearProactiveRefresh();
            throw new RefreshTokenExpiredError();
         }

         // ---- 临时失败：网络超时/无连接 → 保留 token，下次再试 ----
         if (err instanceof RequestError) {
            if (err.type === RequestErrorType.NETWORK || err.type === RequestErrorType.TIMEOUT) {
               logger.warn('auth', 'Token 刷新临时失败（网络问题），保留现有 token', err);
               return null; // 临时失败，不登出
            }
         }

         // ---- HTTP/Business 错误（如 5xx）→ 保守清除 ----
         logger.warn('auth', 'Token 刷新失败（HTTP/Business 错误），强制登出', err instanceof Error ? err : undefined);
         clearStoredRefreshToken();
         clearProactiveRefresh();
         throw new RefreshTokenExpiredError();
      }
   });
}
