/**
 * Request Deduplication Service
 * FE-API-016: Deduplicate identical concurrent requests
 *
 * Prevents duplicate API calls by sharing the same promise for identical concurrent requests
 */

import { AxiosRequestConfig } from 'axios';
import { logger } from '@/utils/logger';

/**
 * Options for request deduplication
 */
export interface DeduplicationOptions {
  /** Whether to enable deduplication for this request (default: true) */
  enabled?: boolean;
  /** Maximum time to keep a request in cache after completion (ms) */
  cacheTime?: number;
}

/**
 * Cached request with its promise
 */
interface CachedRequest {
  promise: Promise<any>;
  timestamp: number;
  resolveCount: number;
}

/**
 * Request Deduplication Service
 * Manages concurrent identical requests by sharing promises
 */
class RequestDeduplicationService {
  private cache: Map<string, CachedRequest> = new Map();
  private defaultCacheTime = 1000; // 1 second default cache time

  /**
   * Generate a unique key for a request
   * Considers method, URL, params, and body
   */
  private generateRequestKey(config: AxiosRequestConfig): string {
    const method = (config.method || 'GET').toUpperCase();
    const url = config.url || '';
    const baseURL = config.baseURL || '';
    const fullUrl = url.startsWith('http') ? url : `${baseURL}${url}`;

    // Sort params for consistent key generation
    const params = config.params
      ? Object.keys(config.params)
          .sort()
          .map((key) => `${key}=${JSON.stringify(config.params![key])}`)
          .join('&')
      : '';

    // Serialize body for consistent key generation
    let bodyKey = '';
    if (config.data) {
      if (config.data instanceof FormData) {
        // For FormData, we can't easily serialize, so use a hash or skip
        // For now, we'll include a flag that it's FormData
        bodyKey = '[FormData]';
      } else {
        try {
          bodyKey = JSON.stringify(config.data);
        } catch {
          bodyKey = String(config.data);
        }
      }
    }

    return `${method}:${fullUrl}${params ? `?${params}` : ''}${bodyKey ? `|${bodyKey}` : ''}`;
  }

  /**
   * Check if deduplication should be enabled for a request
   * Some requests (like POST with different data) should not be deduplicated
   */
  private shouldDeduplicate(config: AxiosRequestConfig): boolean {
    const method = (config.method || 'GET').toUpperCase();

    // Always deduplicate GET, HEAD, OPTIONS requests
    if (['GET', 'HEAD', 'OPTIONS'].includes(method)) {
      return true;
    }

    // For mutations, only deduplicate if explicitly enabled
    // This prevents accidental deduplication of different POST requests
    const deduplicationEnabled =
      (config as any)?._enableDeduplication !== false;

    // Don't deduplicate if explicitly disabled
    if ((config as any)?._disableDeduplication === true) {
      return false;
    }

    return deduplicationEnabled;
  }

  /**
   * Get or create a request promise
   * If an identical request is already in progress, returns the same promise
   */
  async getOrCreateRequest<T>(
    config: AxiosRequestConfig,
    requestFn: () => Promise<T>,
    options: DeduplicationOptions = {},
  ): Promise<T> {
    const { enabled = true, cacheTime = this.defaultCacheTime } = options;

    // Check if deduplication is enabled
    if (!enabled || !this.shouldDeduplicate(config)) {
      return requestFn();
    }

    const key = this.generateRequestKey(config);
    const cached = this.cache.get(key);

    // If request is already in progress, return the same promise
    if (cached) {
      cached.resolveCount++;
      logger.debug(
        `[RequestDeduplication] Reusing request: ${config.method?.toUpperCase()} ${config.url}`,
        {
          key,
          resolveCount: cached.resolveCount,
        },
      );
      return cached.promise;
    }

    // Create new request promise
    const promise = requestFn()
      .then((result) => {
        // Keep in cache for a short time after completion
        // This helps with rapid successive calls
        setTimeout(() => {
          const cached = this.cache.get(key);
          if (cached && cached.promise === promise) {
            this.cache.delete(key);
            logger.debug(`[RequestDeduplication] Removed from cache: ${key}`);
          }
        }, cacheTime);

        return result;
      })
      .catch((error) => {
        // Remove from cache immediately on error
        const cached = this.cache.get(key);
        if (cached && cached.promise === promise) {
          this.cache.delete(key);
          logger.debug(
            `[RequestDeduplication] Removed from cache (error): ${key}`,
          );
        }
        throw error;
      });

    // Store in cache
    this.cache.set(key, {
      promise,
      timestamp: Date.now(),
      resolveCount: 1,
    });

    logger.debug(
      `[RequestDeduplication] New request: ${config.method?.toUpperCase()} ${config.url}`,
      {
        key,
        cacheSize: this.cache.size,
      },
    );

    return promise;
  }

  /**
   * Clear the cache
   */
  clearCache(): void {
    const size = this.cache.size;
    this.cache.clear();
    logger.info(`[RequestDeduplication] Cache cleared (${size} entries)`);
  }

  /**
   * Get cache statistics
   */
  getCacheStats(): {
    size: number;
    entries: Array<{ key: string; resolveCount: number; age: number }>;
  } {
    const entries = Array.from(this.cache.entries()).map(([key, cached]) => ({
      key,
      resolveCount: cached.resolveCount,
      age: Date.now() - cached.timestamp,
    }));

    return {
      size: this.cache.size,
      entries,
    };
  }

  /**
   * Clean up old cache entries
   * Removes entries older than the specified age
   */
  cleanup(maxAge: number = 60000): void {
    const now = Date.now();
    let removed = 0;

    for (const [key, cached] of this.cache.entries()) {
      if (now - cached.timestamp > maxAge) {
        this.cache.delete(key);
        removed++;
      }
    }

    if (removed > 0) {
      logger.debug(
        `[RequestDeduplication] Cleaned up ${removed} old cache entries`,
      );
    }
  }
}

// Singleton instance
export const requestDeduplication = new RequestDeduplicationService();

// Periodic cleanup (every 5 minutes)
if (typeof window !== 'undefined') {
  setInterval(
    () => {
      requestDeduplication.cleanup(60000); // Remove entries older than 1 minute
    },
    5 * 60 * 1000,
  );
}
