/**
 * Rate limiter for Meta Marketing API
 * 
 * Meta uses three types of rate limiting:
 * 1. App-level: x-app-usage (calls per hour)
 * 2. Business use case: x-business-use-case-usage (per business, per hour)
 * 3. Ad account-level: x-ad-account-usage (200 calls/hour per ad account)
 */

interface UsageMetrics {
  callCount: number;
  totalCPUTime: number;
  totalTime: number;
  estimatedTimeToRegainAccess?: number;
}

interface ParsedUsage {
  app?: UsageMetrics;
  business?: Record<string, UsageMetrics>;
  adAccount?: Record<string, UsageMetrics>;
}

interface QueuedRequest {
  execute: () => Promise<void>;
  resolve: (value: void) => void;
  reject: (error: Error) => void;
  priority: number;
}

export class RateLimiter {
  private queue: QueuedRequest[] = [];
  private activeRequests = 0;
  private maxConcurrent: number;
  private usage: ParsedUsage = {};
  private backoffUntil: number = 0;
  private backoffMultiplier = 1;

  // Threshold percentages for throttling
  private readonly THROTTLE_THRESHOLD = 75; // Start slowing at 75%
  private readonly PAUSE_THRESHOLD = 90; // Pause at 90%

  constructor(maxConcurrent: number = 5) {
    this.maxConcurrent = maxConcurrent;
  }

  /**
   * Parse Meta API usage headers
   */
  parseUsageHeaders(headers: Headers): void {
    // Parse app usage
    const appUsage = headers.get('x-app-usage');
    if (appUsage) {
      try {
        this.usage.app = JSON.parse(appUsage);
      } catch (error) {
        console.warn('Failed to parse x-app-usage header:', error);
      }
    }

    // Parse business use case usage
    const businessUsage = headers.get('x-business-use-case-usage');
    if (businessUsage) {
      try {
        this.usage.business = JSON.parse(businessUsage);
      } catch (error) {
        console.warn('Failed to parse x-business-use-case-usage header:', error);
      }
    }

    // Parse ad account usage
    const adAccountUsage = headers.get('x-ad-account-usage');
    if (adAccountUsage) {
      try {
        const parsed = JSON.parse(adAccountUsage);
        this.usage.adAccount = { default: parsed };
      } catch (error) {
        console.warn('Failed to parse x-ad-account-usage header:', error);
      }
    }

    // Check if we're being throttled
    this.checkThrottling();
  }

  /**
   * Check usage metrics and apply backoff if needed
   */
  private checkThrottling(): void {
    let maxUsage = 0;
    let estimatedWait = 0;

    // Check app usage
    if (this.usage.app) {
      maxUsage = Math.max(maxUsage, this.usage.app.callCount);
      if (this.usage.app.estimatedTimeToRegainAccess) {
        estimatedWait = Math.max(estimatedWait, this.usage.app.estimatedTimeToRegainAccess);
      }
    }

    // Check business usage
    if (this.usage.business) {
      for (const businessMetrics of Object.values(this.usage.business)) {
        maxUsage = Math.max(maxUsage, businessMetrics.callCount);
        if (businessMetrics.estimatedTimeToRegainAccess) {
          estimatedWait = Math.max(estimatedWait, businessMetrics.estimatedTimeToRegainAccess);
        }
      }
    }

    // Check ad account usage
    if (this.usage.adAccount) {
      for (const accountMetrics of Object.values(this.usage.adAccount)) {
        maxUsage = Math.max(maxUsage, accountMetrics.callCount);
        if (accountMetrics.estimatedTimeToRegainAccess) {
          estimatedWait = Math.max(estimatedWait, accountMetrics.estimatedTimeToRegainAccess);
        }
      }
    }

    // Apply backoff based on usage percentage
    if (maxUsage >= this.PAUSE_THRESHOLD) {
      // At 90%+, apply exponential backoff
      const backoffTime = estimatedWait > 0 ? estimatedWait : 60000 * this.backoffMultiplier;
      this.backoffUntil = Date.now() + backoffTime;
      this.backoffMultiplier = Math.min(this.backoffMultiplier * 2, 16);
      console.warn(`Rate limit approaching (${maxUsage}%), backing off for ${backoffTime}ms`);
    } else if (maxUsage >= this.THROTTLE_THRESHOLD) {
      // At 75-89%, apply mild delay
      const throttleDelay = 1000 * Math.pow((maxUsage - this.THROTTLE_THRESHOLD) / 15, 2);
      this.backoffUntil = Date.now() + throttleDelay;
      console.warn(`Rate limit at ${maxUsage}%, throttling for ${throttleDelay}ms`);
    } else {
      // Reset backoff multiplier when usage is healthy
      this.backoffMultiplier = 1;
    }
  }

  /**
   * Queue a request for execution
   */
  async enqueue<T>(fn: () => Promise<T>, priority: number = 0): Promise<T> {
    return new Promise<T>((resolve, reject) => {
      const request: QueuedRequest = {
        execute: async () => {
          try {
            const result = await fn();
            resolve(result);
          } catch (error) {
            reject(error instanceof Error ? error : new Error(String(error)));
          }
        },
        resolve: () => {},
        reject,
        priority,
      };

      this.queue.push(request);
      this.processQueue();
    });
  }

  /**
   * Process the request queue
   */
  private async processQueue(): Promise<void> {
    // Check if we're in backoff period
    if (this.backoffUntil > Date.now()) {
      const waitTime = this.backoffUntil - Date.now();
      await new Promise((resolve) => setTimeout(resolve, waitTime));
    }

    // Process requests up to max concurrent limit
    while (this.queue.length > 0 && this.activeRequests < this.maxConcurrent) {
      // Sort by priority (higher priority first)
      this.queue.sort((a, b) => b.priority - a.priority);
      
      const request = this.queue.shift();
      if (!request) break;

      this.activeRequests++;
      
      request.execute().finally(() => {
        this.activeRequests--;
        this.processQueue();
      });
    }
  }

  /**
   * Get current usage metrics
   */
  getUsage(): ParsedUsage {
    return this.usage;
  }

  /**
   * Get current queue length
   */
  getQueueLength(): number {
    return this.queue.length;
  }

  /**
   * Get active request count
   */
  getActiveCount(): number {
    return this.activeRequests;
  }

  /**
   * Check if rate limited
   */
  isRateLimited(): boolean {
    return this.backoffUntil > Date.now();
  }

  /**
   * Get time until rate limit expires (ms)
   */
  getBackoffTime(): number {
    return Math.max(0, this.backoffUntil - Date.now());
  }

  /**
   * Reset the rate limiter state
   */
  reset(): void {
    this.usage = {};
    this.backoffUntil = 0;
    this.backoffMultiplier = 1;
  }
}
