import { Injectable, ExecutionContext } from '@nestjs/common';
import { ThrottlerGuard, ThrottlerException } from '@nestjs/throttler';
import { ApiTier } from './api-key.guard';

/**
 * 分层限流守卫：按 API tier 应用不同的速率限制
 *
 * 指南 §3.4 限流策略：
 *   public:       60 req/min,  1,000 req/hour
 *   institutional: 600 req/min, 50,000 req/hour
 *   regulatory:    unlimited
 *   internal:      unlimited
 */
@Injectable()
export class TierThrottlerGuard extends ThrottlerGuard {
  private static readonly TIER_LIMITS: Record<ApiTier, { minuteLimit: number; hourLimit: number }> = {
    public:        { minuteLimit: 60,  hourLimit: 1000 },
    institutional: { minuteLimit: 600, hourLimit: 50000 },
    regulatory:    { minuteLimit: 0,   hourLimit: 0 },     // 0 = unlimited
    internal:      { minuteLimit: 0,   hourLimit: 0 },
  };

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const tier: ApiTier = request.apiTier || 'public';

    const limits = TierThrottlerGuard.TIER_LIMITS[tier];

    // regulatory 和 internal 不限流
    if (limits.minuteLimit === 0) {
      return true;
    }

    // 使用 API key 作为限流 key（比 IP 更精确）
    const apiKey = request.headers['x-api-key'] as string || request.ip;

    // 分钟级限流检查
    const minuteKey = `throttle:min:${apiKey}`;
    const minuteCount = this.incrementCounter(minuteKey, 60);
    if (await minuteCount > limits.minuteLimit) {
      throw new ThrottlerException(`Rate limit exceeded: ${limits.minuteLimit} requests/minute for ${tier} tier`);
    }

    // 小时级限流检查
    const hourKey = `throttle:hour:${apiKey}`;
    const hourCount = this.incrementCounter(hourKey, 3600);
    if (await hourCount > limits.hourLimit) {
      throw new ThrottlerException(`Rate limit exceeded: ${limits.hourLimit} requests/hour for ${tier} tier`);
    }

    return true;
  }

  /**
   * 内存计数器（生产环境应替换为 Redis INCR + TTL）
   */
  private counters = new Map<string, { count: number; expiresAt: number }>();

  private async incrementCounter(key: string, ttlSeconds: number): Promise<number> {
    const now = Date.now();
    const entry = this.counters.get(key);

    if (!entry || entry.expiresAt <= now) {
      this.counters.set(key, { count: 1, expiresAt: now + ttlSeconds * 1000 });
      return 1;
    }

    entry.count++;
    return entry.count;
  }
}
