import { createModuleLogger } from '../../utils/logger.js';
import type { RateLimitConfig } from '../../types/queue.js';

const log = createModuleLogger('rate-limiter');

export type ServiceName = 'openrouter' | 'kieApi' | 'telegramBotApi';

interface WindowEntry {
  timestamp: number;
}

const DEFAULT_CONFIG: RateLimitConfig = {
  openrouter: { requestsPerSecond: 5, requestsPerMinute: 60 },
  kieApi: { requestsPerSecond: 2, requestsPerMinute: 30 },
  telegramBotApi: { requestsPerSecond: 30, messagesPerChatPerSecond: 1 },
};

export class RateLimiter {
  private config: RateLimitConfig;
  private windows: Map<string, WindowEntry[]> = new Map();
  private backoffCounts: Map<string, number> = new Map();

  constructor(config?: Partial<RateLimitConfig>) {
    this.config = { ...DEFAULT_CONFIG, ...config };
  }

  async acquire(service: ServiceName, chatId?: string): Promise<{ allowed: boolean; waitMs: number }> {
    const key = chatId ? `${service}:${chatId}` : service;
    const now = Date.now();

    // Get or create window
    if (!this.windows.has(key)) {
      this.windows.set(key, []);
    }
    const window = this.windows.get(key)!;

    // Clean old entries (older than 60 seconds)
    const cutoff = now - 60000;
    const cleaned = window.filter(e => e.timestamp > cutoff);
    this.windows.set(key, cleaned);

    // Check per-second limit
    const oneSecAgo = now - 1000;
    const recentSecond = cleaned.filter(e => e.timestamp > oneSecAgo);
    const perSecLimit = this.getPerSecondLimit(service, !!chatId);

    if (recentSecond.length >= perSecLimit) {
      const waitMs = this.calculateBackoff(key);
      log.warn({ service, key, waitMs, current: recentSecond.length, limit: perSecLimit }, 'Rate limit hit (per-second)');
      return { allowed: false, waitMs };
    }

    // Check per-minute limit
    const perMinLimit = this.getPerMinuteLimit(service);

    if (perMinLimit !== null && cleaned.length >= perMinLimit) {
      const waitMs = this.calculateBackoff(key);
      log.warn({ service, key, waitMs, current: cleaned.length, limit: perMinLimit }, 'Rate limit hit (per-minute)');
      return { allowed: false, waitMs };
    }

    // Allowed - record the request
    cleaned.push({ timestamp: now });
    this.backoffCounts.set(key, 0); // Reset backoff on success
    return { allowed: true, waitMs: 0 };
  }

  recordRequest(service: ServiceName, chatId?: string): void {
    const key = chatId ? `${service}:${chatId}` : service;
    if (!this.windows.has(key)) {
      this.windows.set(key, []);
    }
    this.windows.get(key)!.push({ timestamp: Date.now() });
  }

  private getPerSecondLimit(service: ServiceName, isChat: boolean): number {
    if (service === 'telegramBotApi' && isChat) {
      return this.config.telegramBotApi.messagesPerChatPerSecond;
    }
    const cfg = this.config[service];
    return 'requestsPerSecond' in cfg ? cfg.requestsPerSecond : 30;
  }

  private getPerMinuteLimit(service: ServiceName): number | null {
    const cfg = this.config[service];
    if ('requestsPerMinute' in cfg) return cfg.requestsPerMinute;
    return null;
  }

  private calculateBackoff(key: string): number {
    const count = this.backoffCounts.get(key) || 0;
    this.backoffCounts.set(key, count + 1);
    // Exponential backoff: 1s, 2s, 4s, 8s, 16s, max 60s
    return Math.min(1000 * Math.pow(2, count), 60000);
  }

  getBackoffCount(service: ServiceName, chatId?: string): number {
    const key = chatId ? `${service}:${chatId}` : service;
    return this.backoffCounts.get(key) || 0;
  }

  isMaxRetriesExceeded(service: ServiceName, maxRetries = 5, chatId?: string): boolean {
    return this.getBackoffCount(service, chatId) >= maxRetries;
  }

  resetBackoff(service: ServiceName, chatId?: string): void {
    const key = chatId ? `${service}:${chatId}` : service;
    this.backoffCounts.set(key, 0);
  }

  updateConfig(config: Partial<RateLimitConfig>): void {
    this.config = { ...this.config, ...config };
  }

  getConfig(): RateLimitConfig {
    return JSON.parse(JSON.stringify(this.config));
  }
}

export const rateLimiter = new RateLimiter();
