/**
 * Redis 分布式速率限制器（audit-P1-6）
 *
 * 基于 `@upstash/ratelimit` + `@upstash/redis` 的滑动窗口实现。
 *
 * 适用场景：
 * - 多实例部署（K8s 水平扩容、Serverless 多实例）
 * - 需要跨实例共享计数的限流
 *
 * 使用前提：
 * - 项目需安装 `@upstash/ratelimit` 与 `@upstash/redis`
 * - 环境变量需配置 `UPSTASH_REDIS_REST_URL` 与 `UPSTASH_REDIS_REST_TOKEN`
 *
 * 依赖通过动态 import 加载，未启用 Redis 时不会引入这两个包的体积。
 * 若启用但未安装依赖，将在首次调用时抛错。
 *
 * upstash/ratelimit 的 `Ratelimit` 实例在构造时需固定 `max` 与 `window`，
 * 因此本实现按 `${limit}:${windowMs}` 签名缓存多个 Ratelimit 实例，
 * 同一规则的所有调用复用同一实例。
 */

import { env } from "@/env.mjs"

import type { RateLimiter, RateLimitParams, RateLimitResult } from "./types"

/** 毫秒转 upstash `Ratelimit.slidingWindow` 接受的字符串时间单位 */
function msToSlidingWindowArg(ms: number): string {
  if (ms % (60 * 1000) === 0) {
    const minutes = ms / (60 * 1000)
    return `${minutes} m`
  }
  if (ms % 1000 === 0) {
    const seconds = ms / 1000
    return `${seconds} s`
  }
  return `${ms} ms`
}

/** upstash ratelimit 调用返回的结果类型（最小可用子集） */
interface UpstashRatelimitResult {
  success: boolean
  remaining: number
  reset: number
}

/** upstash Ratelimit 实例的最小可用接口 */
interface UpstashRatelimitInstance {
  limit: (identifier: string) => Promise<UpstashRatelimitResult>
  reset?: (identifier: string) => Promise<void>
}

/** 动态加载后获得的 Ratelimit 构造器 */
interface UpstashRatelimitCtor {
  new (config: {
    redis: unknown
    limiter: unknown
    analytics?: boolean
    prefix?: string
  }): UpstashRatelimitInstance
  slidingWindow: (
    max: number,
    window: string,
  ) => { type: "sliding-window"; max: number; window: string }
}

/**
 * Redis 实现的速率限制器。
 *
 * 通过动态 import 加载 `@upstash/ratelimit` 与 `@upstash/redis`，
 * 避免在内存模式下引入这两个包的体积。
 *
 * 故障策略：
 * - 若 Redis 调用抛错，返回 `{ success: true, remaining: limit - 1 }` 兜底，
 *   避免限流后端故障导致主流程被阻断（限流降级原则）
 * - 配置缺失（未设置 UPSTASH_REDIS_REST_URL/TOKEN）时在构造时即抛错
 */
export class RedisRateLimiter implements RateLimiter {
  /** 按 `${limit}:${windowMs}` 缓存 Ratelimit 实例，避免重复构造 */
  private readonly instances = new Map<string, UpstashRatelimitInstance>()
  /** 共享的 Redis 客户端，避免重复连接 */
  private redisClient: unknown = null
  /** 加载 Ratelimit 构造器的 Promise（懒加载） */
  private ratelimitCtorPromise: Promise<UpstashRatelimitCtor> | null = null

  constructor() {
    if (!env.UPSTASH_REDIS_REST_URL || !env.UPSTASH_REDIS_REST_TOKEN) {
      throw new Error(
        "RedisRateLimiter requires UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN. " +
          "Set RATE_LIMIT_DRIVER=memory to use in-memory limiter instead.",
      )
    }
  }

  /** 懒加载 @upstash/redis 的 Redis 客户端 */
  private async getRedisClient(): Promise<unknown> {
    if (this.redisClient) return this.redisClient
    // webpackIgnore: 让 @upstash/redis 成为运行时可选依赖，未启用 redis 驱动时不要求安装
    const { Redis } = await import(/* webpackIgnore: true */ "@upstash/redis")
    this.redisClient = new Redis({
      url: env.UPSTASH_REDIS_REST_URL!,
      token: env.UPSTASH_REDIS_REST_TOKEN!,
    })
    return this.redisClient
  }

  /** 懒加载 @upstash/ratelimit 的 Ratelimit 构造器 */
  private async getRatelimitCtor(): Promise<UpstashRatelimitCtor> {
    if (this.ratelimitCtorPromise) return this.ratelimitCtorPromise
    this.ratelimitCtorPromise = (async () => {
      // webpackIgnore: 让 @upstash/ratelimit 成为运行时可选依赖，未启用 redis 驱动时不要求安装
      const mod = await import(/* webpackIgnore: true */ "@upstash/ratelimit")
      return mod.Ratelimit as unknown as UpstashRatelimitCtor
    })()
    return this.ratelimitCtorPromise
  }

  /** 按 (limit, windowMs) 获取或创建 Ratelimit 实例 */
  private async getRatelimit(
    params: RateLimitParams,
  ): Promise<UpstashRatelimitInstance> {
    const signature = `${params.limit}:${params.windowMs}`
    const existing = this.instances.get(signature)
    if (existing) return existing

    const [Ratelimit, redis] = await Promise.all([
      this.getRatelimitCtor(),
      this.getRedisClient(),
    ])

    const instance = new Ratelimit({
      redis,
      limiter: Ratelimit.slidingWindow(
        params.limit,
        msToSlidingWindowArg(params.windowMs),
      ),
      analytics: false,
      // 加 prefix 避免与其它用途的 Redis key 冲突
      prefix: "next-edu:rl",
    })
    this.instances.set(signature, instance)
    return instance
  }

  async limit(params: RateLimitParams): Promise<RateLimitResult> {
    try {
      const ratelimit = await this.getRatelimit(params)
      const result = await ratelimit.limit(params.key)

      const now = Date.now()
      const resetTime =
        typeof result.reset === "number" && result.reset > 0
          ? result.reset * 1000
          : now + params.windowMs
      const retryAfterMs = result.success ? 0 : Math.max(0, resetTime - now)

      return {
        success: result.success,
        remaining: result.remaining,
        resetTime,
        retryAfterMs,
      }
    } catch (error) {
      // 限流后端故障时降级为放行，避免阻断主流程
      console.error(
        "[rate-limit] Redis backend failure, falling back to allow:",
        error instanceof Error ? error.message : String(error),
      )
      const resetTime = Date.now() + params.windowMs
      return {
        success: true,
        remaining: Math.max(0, params.limit - 1),
        resetTime,
        retryAfterMs: 0,
      }
    }
  }

  async reset(key: string): Promise<void> {
    try {
      // 找到包含该 key 的实例（实例按规则签名缓存，key 可能命中多个）
      // 由于 upstash/ratelimit 的 reset 接受 identifier，遍历所有实例重置
      for (const instance of this.instances.values()) {
        if (typeof instance.reset === "function") {
          await instance.reset(key)
        }
      }
    } catch (error) {
      // reset 失败不阻断主流程
      console.error(
        "[rate-limit] Redis reset failure, ignoring:",
        error instanceof Error ? error.message : String(error),
      )
    }
  }
}
