import { RedisService } from '@shieldai/shared-notifications';
import { TierRateLimits, SubscriptionTier, spamRateLimits } from '../config/spamshield.config';

export interface RateLimitStatus {
  exceeded: boolean;
  limit: number;
  remaining: number;
  resetAt: Date;
  retryAfterSeconds: number;
}

export interface RateLimitOptions {
  windowMs?: number;
  dailyWindowMs?: number;
}

export class SpamRateLimitMiddleware {
  private redisService: RedisService;
  private options: RateLimitOptions;

  constructor(redisService: RedisService, options?: RateLimitOptions) {
    this.redisService = redisService;
    this.options = {
      windowMs: options?.windowMs || 60000,
      dailyWindowMs: options?.dailyWindowMs || 86400000,
    };
  }

  private getMinuteKey(userId: string, tier: SubscriptionTier): string {
    const windowMs = this.options.windowMs ?? 60000;
    const minuteTimestamp = Math.floor(Date.now() / windowMs);
    return `ratelimit:spam:${userId}:${tier}:min:${minuteTimestamp}`;
  }

  private getDayKey(userId: string, tier: SubscriptionTier): string {
    const date = new Date().toISOString().split('T')[0];
    return `ratelimit:spam:${userId}:${tier}:day:${date}`;
  }

  private getResetTime(windowMs: number): Date {
    const now = Date.now();
    const resetTimestamp = Math.ceil(now / windowMs) * windowMs;
    return new Date(resetTimestamp);
  }

  async checkLimit(
    userId: string,
    tier: SubscriptionTier,
  ): Promise<RateLimitStatus> {
    const tierLimits = spamRateLimits[tier];
    const minuteKey = this.getMinuteKey(userId, tier);
    const dayKey = this.getDayKey(userId, tier);

    try {
      const [minuteCount, dayCount] = await Promise.all([
        this.redisService.getCounter(minuteKey),
        this.redisService.getCounter(dayKey),
      ]);

      const minuteExceeded = minuteCount >= tierLimits.perMinute;
      const dayExceeded = dayCount >= tierLimits.perDay;
      const exceeded = minuteExceeded || dayExceeded;

      const effectiveLimit = exceeded
        ? Math.min(tierLimits.perMinute, tierLimits.perDay)
        : Math.min(tierLimits.perMinute, tierLimits.perDay);

      const effectiveCount = exceeded
        ? Math.min(minuteCount, dayCount)
        : Math.min(minuteCount, dayCount);

      const windowMs = this.options.windowMs ?? 60000;
      return {
        exceeded,
        limit: effectiveLimit,
        remaining: Math.max(0, effectiveLimit - effectiveCount),
        resetAt: this.getResetTime(windowMs),
        retryAfterSeconds: Math.ceil(
          (this.getResetTime(windowMs).getTime() - Date.now()) / 1000,
        ),
      };
    } catch (error) {
      console.error('[SpamRateLimit] Redis error:', error);
      const windowMs = this.options.windowMs ?? 60000;
      return {
        exceeded: false,
        limit: tierLimits.perMinute,
        remaining: tierLimits.perMinute,
        resetAt: this.getResetTime(windowMs),
        retryAfterSeconds: windowMs / 1000,
      };
    }
  }

  async incrementCounter(
    userId: string,
    tier: SubscriptionTier,
  ): Promise<{ minuteCount: number; dayCount: number }> {
    const minuteKey = this.getMinuteKey(userId, tier);
    const dayKey = this.getDayKey(userId, tier);

    try {
      const windowMs = this.options.windowMs ?? 60000;
      const dailyWindowMs = this.options.dailyWindowMs ?? 86400000;
      const [minuteCount, dayCount] = await Promise.all([
        this.redisService.increment(minuteKey, Math.ceil(windowMs / 1000)),
        this.redisService.increment(dayKey, Math.ceil(dailyWindowMs / 1000)),
      ]);

      return { minuteCount, dayCount };
    } catch (error) {
      console.error('[SpamRateLimit] Increment error:', error);
      return { minuteCount: 0, dayCount: 0 };
    }
  }

  async checkAndIncrement(
    userId: string,
    tier: SubscriptionTier,
  ): Promise<{ allowed: boolean; status: RateLimitStatus }> {
    const status = await this.checkLimit(userId, tier);

    if (!status.exceeded) {
      await this.incrementCounter(userId, tier);
      const updatedStatus = await this.checkLimit(userId, tier);
      return { allowed: true, status: updatedStatus };
    }

    return { allowed: false, status };
  }

  async getUsage(userId: string, tier: SubscriptionTier): Promise<{
    minuteUsed: number;
    minuteLimit: number;
    minuteRemaining: number;
    dayUsed: number;
    dayLimit: number;
    dayRemaining: number;
  }> {
    const tierLimits = spamRateLimits[tier];
    const minuteKey = this.getMinuteKey(userId, tier);
    const dayKey = this.getDayKey(userId, tier);

    try {
      const [minuteCount, dayCount] = await Promise.all([
        this.redisService.getCounter(minuteKey),
        this.redisService.getCounter(dayKey),
      ]);

      return {
        minuteUsed: minuteCount,
        minuteLimit: tierLimits.perMinute,
        minuteRemaining: Math.max(0, tierLimits.perMinute - minuteCount),
        dayUsed: dayCount,
        dayLimit: tierLimits.perDay,
        dayRemaining: Math.max(0, tierLimits.perDay - dayCount),
      };
    } catch (error) {
      console.error('[SpamRateLimit] Usage fetch error:', error);
      return {
        minuteUsed: 0,
        minuteLimit: tierLimits.perMinute,
        minuteRemaining: tierLimits.perMinute,
        dayUsed: 0,
        dayLimit: tierLimits.perDay,
        dayRemaining: tierLimits.perDay,
      };
    }
  }
}

export function createSpamRateLimitMiddleware(
  redisService: RedisService,
  options?: RateLimitOptions,
): SpamRateLimitMiddleware {
  return new SpamRateLimitMiddleware(redisService, options);
}
