import { Provide, Scope, ScopeEnum } from '@midwayjs/core';

/**
 * 频率限制器的入参选项 — 可只指定其中一部分维度
 */
export interface RateLimiterOptions {
  /** 同 (appId, targetKey) 每分钟最大命中次数 */
  perTargetPerMin?: number;
  /** 同 appId 每分钟最大命中次数（跨所有 target） */
  perAppPerMin?: number;
  /** 同 appId 每天最大命中次数 */
  perAppPerDay?: number;
}

/**
 * tryAcquire 的返回结果
 * - allowed:true  — 本次请求被放行
 * - allowed:false — 本次请求被拦截，reason 说明触发了哪个维度的限制
 */
export type RateLimitResult =
  | { allowed: true }
  | { allowed: false; reason: string };

/**
 * 内存令牌桶频率限制器（per-target-per-min / per-app-per-min / per-app-per-day 三维）
 *
 * Singleton。在 tryAcquire 时按 wall clock 滑动窗口判断。
 * 不依赖外部存储，后端重启重置（MVP 可接受）。
 */
@Provide()
@Scope(ScopeEnum.Singleton)
export class RateLimiter {
  /**
   * 每条记录保存命中时间戳(ms)，key = appId + ':' + targetKey
   * tryAcquire 时清理窗口外的过期项
   */
  private targetHits = new Map<string, number[]>();

  /**
   * 每条记录保存命中时间戳(ms)，key = appId
   * 用于 per-app-per-min 和 per-app-per-day 两个维度
   */
  private appHits = new Map<string, number[]>();

  /**
   * 尝试占用一次配额。
   * 按优先顺序检查：perTargetPerMin → perAppPerMin → perAppPerDay。
   * 任意维度超限则立即返回 { allowed:false, reason }，不记录本次命中。
   * 三个维度均未超限则记录命中并返回 { allowed:true }。
   *
   * @param appId     应用标识
   * @param targetKey 目标标识（例如群名、联系人 ID 等）
   * @param opts      限制选项，未指定的维度不做检查
   */
  tryAcquire(appId: string, targetKey: string, opts: RateLimiterOptions): RateLimitResult {
    const now = Date.now();
    // 1 分钟前的时间戳
    const oneMinAgo = now - 60_000;
    // 24 小时前的时间戳
    const oneDayAgo = now - 24 * 60 * 60_000;

    // 复合 key：appId + ':' + targetKey
    const targetCompositeKey = `${appId}:${targetKey}`;

    // 清理过期记录（保留分钟内的，targetArr 用于 per-target-per-min）
    const targetArr = (this.targetHits.get(targetCompositeKey) ?? []).filter(t => t >= oneMinAgo);

    // appArr 保留 24 小时内的（覆盖 per-app-per-min 和 per-app-per-day 两个维度）
    const appArr = (this.appHits.get(appId) ?? []).filter(t => t >= oneDayAgo);

    // ── 维度检查 ──────────────────────────────────────────────

    // 检查 per-target-per-min
    if (opts.perTargetPerMin != null && targetArr.length >= opts.perTargetPerMin) {
      // 更新清理后的数组（去除过期项），但不追加本次
      this.targetHits.set(targetCompositeKey, targetArr);
      this.appHits.set(appId, appArr);
      return { allowed: false, reason: `per-target-per-min(${opts.perTargetPerMin}) exceeded` };
    }

    // 检查 per-app-per-min（从 appArr 中再过滤出最近 1 分钟的）
    const appLastMin = appArr.filter(t => t >= oneMinAgo).length;
    if (opts.perAppPerMin != null && appLastMin >= opts.perAppPerMin) {
      this.targetHits.set(targetCompositeKey, targetArr);
      this.appHits.set(appId, appArr);
      return { allowed: false, reason: `per-app-per-min(${opts.perAppPerMin}) exceeded` };
    }

    // 检查 per-app-per-day（appArr 已经是 24 小时内的）
    if (opts.perAppPerDay != null && appArr.length >= opts.perAppPerDay) {
      this.targetHits.set(targetCompositeKey, targetArr);
      this.appHits.set(appId, appArr);
      return { allowed: false, reason: `per-app-per-day(${opts.perAppPerDay}) exceeded` };
    }

    // ── 所有维度均通过 → 记录本次命中 ────────────────────────

    targetArr.push(now);
    appArr.push(now);
    this.targetHits.set(targetCompositeKey, targetArr);
    this.appHits.set(appId, appArr);

    return { allowed: true };
  }

  /**
   * 清空所有内部计数器（测试辅助方法，生产慎用）
   */
  reset(): void {
    this.targetHits.clear();
    this.appHits.clear();
  }
}
