/**
 * 统一请求封装
 *
 * - 自动附加 Authorization: Bearer <token>
 * - 401 时自动尝试 refresh_token 刷新，刷新成功后重试原请求
 * - 刷新失败时清除 token 并跳转 login 页面
 * - BASE_URL 从 config.ts 读取（按运行环境自动切换）
 */

import { API_BASE } from "./config"

export interface RequestOptions {
  url: string
  method?: "GET" | "POST" | "PUT" | "DELETE"
  data?: any
  header?: Record<string, string>
  /** 是否需要认证，默认 true */
  needAuth?: boolean
}

/** 是否正在刷新 token，防止并发刷新 */
let isRefreshing = false
/** 刷新期间排队的请求（等刷新完成后统一重试） */
let pendingQueue: Array<{
  resolve: (value: any) => void
  reject: (reason: any) => void
  options: RequestOptions
}> = []

/**
 * 从 globalData 或 Storage 获取 access_token
 */
function getAccessToken(): string | undefined {
  const app = getApp<IAppOption>()
  return app.globalData.token || wx.getStorageSync("token") || undefined
}

/**
 * 从 globalData 或 Storage 获取 refresh_token
 */
function getRefreshToken(): string | undefined {
  const app = getApp<IAppOption>()
  return app.globalData.refreshToken || wx.getStorageSync("refreshToken") || undefined
}

/**
 * 清除本地存储的所有 token
 */
function clearTokens(): void {
  const app = getApp<IAppOption>()
  app.globalData.token = undefined
  app.globalData.refreshToken = undefined
  wx.removeStorageSync("token")
  wx.removeStorageSync("refreshToken")
}

/**
 * 保存 token 到 globalData 和 Storage
 */
function saveTokens(accessToken: string, refreshToken: string): void {
  const app = getApp<IAppOption>()
  app.globalData.token = accessToken
  app.globalData.refreshToken = refreshToken
  wx.setStorageSync("token", accessToken)
  wx.setStorageSync("refreshToken", refreshToken)
}

/**
 * 跳转到登录页（使用 reLaunch 清空页面栈）
 */
function redirectToLogin(): void {
  wx.reLaunch({ url: "/pages/login/login" })
}

/**
 * 执行底层 wx.request，返回 Promise
 */
function wxRequest(options: RequestOptions): Promise<any> {
  return new Promise((resolve, reject) => {
    wx.request({
      url: `${API_BASE}${options.url}`,
      method: options.method || "GET",
      data: options.data,
      header: options.header || {},
      success(res) {
        if (res.statusCode >= 200 && res.statusCode < 300) {
          // CHANGE 2026-03-23 | 响应解包
          // 后端 ResponseWrapperMiddleware 将 2xx JSON 包装为 { code: 0, data: <原始body> }
          // 前端统一解包，调用方直接拿到业务数据
          const body = res.data as any
          if (body && typeof body === "object" && body.code === 0 && "data" in body) {
            resolve(body.data)
          } else {
            resolve(body)
          }
        } else {
          reject({ statusCode: res.statusCode, data: res.data })
        }
      },
      fail(err) {
        reject({ statusCode: 0, data: err })
      },
    })
  })
}

/**
 * 尝试用 refresh_token 刷新令牌
 *
 * 成功 → 保存新 token 并返回 true
 * 失败 → 清除 token、跳转登录页、返回 false
 *
 * CHANGE 2026-03-22 | 竞态修复
 * 刷新前记录当前 token 快照，刷新失败时检查 token 是否已被
 * onLogin 更新。若已更新，说明用户已重新登录，不清除新 token。
 */
async function tryRefreshToken(): Promise<boolean> {
  const rt = getRefreshToken()
  if (!rt) {
    clearTokens()
    redirectToLogin()
    return false
  }

  // 记录刷新前的 token，用于竞态检测
  const tokenBeforeRefresh = getAccessToken()

  try {
    const data = await wxRequest({
      url: "/api/xcx/refresh",
      method: "POST",
      data: { refresh_token: rt },
      header: { "Content-Type": "application/json" },
      needAuth: false,
    })
    // CHANGE 2026-03-23 | camelCase 修复：后端 CamelModel 序列化输出 camelCase
    if (data.accessToken && data.refreshToken) {
      saveTokens(data.accessToken, data.refreshToken)
      return true
    }
    // 响应格式异常，视为刷新失败
    // 竞态检测：token 已被 onLogin 更新，不清除
    if (getAccessToken() !== tokenBeforeRefresh) {
      return false
    }
    clearTokens()
    redirectToLogin()
    return false
  } catch {
    // 竞态检测：token 已被 onLogin 更新，不清除
    if (getAccessToken() !== tokenBeforeRefresh) {
      return false
    }
    clearTokens()
    redirectToLogin()
    return false
  }
}

// ============================================
// 全局 Loading 管理（引用计数 + 1 秒延迟）
// ============================================

/** 当前活跃请求数 */
let _loadingCount = 0
/** 1 秒延迟定时器 */
let _loadingTimer: ReturnType<typeof setTimeout> | null = null
/** loading 是否已显示 */
let _loadingShown = false

function _onRequestStart(): void {
  _loadingCount++
  if (_loadingCount === 1) {
    // 首个请求，启动 1 秒延迟
    _loadingTimer = setTimeout(() => {
      _loadingTimer = null
      if (_loadingCount > 0) {
        _loadingShown = true
        wx.showLoading({ title: "加载中...", mask: true })
      }
    }, 1000)
  }
}

function _onRequestEnd(): void {
  _loadingCount = Math.max(0, _loadingCount - 1)
  if (_loadingCount === 0) {
    if (_loadingTimer) {
      clearTimeout(_loadingTimer)
      _loadingTimer = null
    }
    if (_loadingShown) {
      _loadingShown = false
      wx.hideLoading()
    }
  }
}

/**
 * 处理排队中的请求：刷新成功后全部重试，失败则全部拒绝
 */
function flushPendingQueue(success: boolean): void {
  const queue = [...pendingQueue]
  pendingQueue = []
  for (const item of queue) {
    if (success) {
      // 重试时会自动附加新 token
      request(item.options).then(item.resolve, item.reject)
    } else {
      item.reject({ statusCode: 401, data: { detail: "刷新令牌失败" } })
    }
  }
}

/**
 * 统一请求入口
 *
 * @param options 请求配置
 * @returns 响应数据（已解析的 JSON）
 */
export function request(options: RequestOptions): Promise<any> {
  const needAuth = options.needAuth !== false
  const headers: Record<string, string> = {
    "Content-Type": "application/json",
    ...options.header,
  }

  // 自动附加 Authorization header
  if (needAuth) {
    const token = getAccessToken()
    if (token) {
      headers["Authorization"] = `Bearer ${token}`
    }
  }

  const finalOptions: RequestOptions = { ...options, header: headers }

  _onRequestStart()
  return wxRequest(finalOptions).then(
    (data) => { _onRequestEnd(); return data },
    async (err) => {
    // 非 401 或不需要认证的请求，直接抛出
    if (err.statusCode !== 401 || !needAuth) {
      _onRequestEnd()
      throw err
    }

    // 401 → 尝试刷新 token
    _onRequestEnd() // 当前请求结束计数（重试会重新计数）
    if (isRefreshing) {
      // 已有刷新请求在进行中，排队等待
      return new Promise((resolve, reject) => {
        pendingQueue.push({ resolve, reject, options })
      })
    }

    isRefreshing = true
    try {
      const ok = await tryRefreshToken()
      flushPendingQueue(ok)
      if (!ok) {
        throw { statusCode: 401, data: { detail: "刷新令牌失败" } }
      }
      // 刷新成功，用新 token 重试原请求
      return request(options)
    } finally {
      isRefreshing = false
    }
  })
}

export default request
