/**
 * Token 缓存和并发请求防重工具类
 * 1. 确保同一个 key 的并发请求只会真正执行一次
 * 2. 缓存 token 并管理过期时间
 */

import { MAX_TOKEN_CACHE_DURATION } from './adapter/base'

interface CacheEntry<T> {
  data: T
  expirationTime: number
}

export default class OssSingleton {
  private static pendingRequests: Map<string, Promise<any>> = new Map()
  private static cache: Map<string, CacheEntry<any>> = new Map()

  /**
   * 获取或创建带缓存的请求
   * @param key 请求的唯一标识
   * @param callback 实际的请求函数
   * @param getCacheDuration 计算缓存时长的函数，接收 callback 返回的数据，返回缓存时长（毫秒）
   * @returns Promise<T>
   */
  public static async getCachedData<T>(
    key: string,
    callback: () => Promise<T>,
    getCacheDuration?: number | ((data: T) => number),
  ): Promise<T> {
    const currentTime = Date.now()

    // 检查缓存是否有效
    const cachedEntry = OssSingleton.cache.get(key)
    if (cachedEntry && currentTime < cachedEntry.expirationTime) {
      return cachedEntry.data as T
    }

    // 如果已经有相同的请求在进行中，直接返回该 Promise
    if (OssSingleton.pendingRequests.has(key)) {
      return OssSingleton.pendingRequests.get(key)! as Promise<T>
    }

    // 创建新的请求 Promise
    const promise = callback()
      .then(data => {
        // 计算缓存时长
        let cacheDuration: number
        if (typeof getCacheDuration === 'function') {
          cacheDuration = getCacheDuration(data)
        } else if (typeof getCacheDuration === 'number') {
          cacheDuration = getCacheDuration
        } else {
          cacheDuration = MAX_TOKEN_CACHE_DURATION
        }

        // 缓存数据并设置过期时间
        OssSingleton.cache.set(key, {
          data,
          expirationTime: Date.now() + cacheDuration,
        })
        return data
      })
      .finally(() => {
        // 请求完成后清理 pending 状态
        OssSingleton.pendingRequests.delete(key)
      })

    OssSingleton.pendingRequests.set(key, promise)
    return promise
  }

  /**
   * 检查缓存是否已过期
   * @param key 缓存键
   * @returns 如果缓存不存在或已过期返回 true，否则返回 false
   */
  public static isCacheExpired(key: string): boolean {
    const cachedEntry = OssSingleton.cache.get(key)
    if (!cachedEntry) {
      return true
    }
    return Date.now() >= cachedEntry.expirationTime
  }
}
