/**
 * Response Cache Service
 * FE-API-017: Response caching for GET requests
 *
 * Caches GET request responses to reduce server load and improve performance
 */

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

/**
 * Cached response with metadata
 */
interface CachedResponse<T = any> {
  data: T;
  headers: Record<string, string>;
  status: number;
  statusText: string;
  timestamp: number;
  etag?: string;
  lastModified?: string;
  maxAge?: number; // Cache max age in seconds
}

/**
 * Cache configuration
 */
export interface CacheConfig {
  /** Default TTL in milliseconds (default: 5 minutes) */
  defaultTTL?: number;
  /** Maximum cache size (default: 100 entries) */
  maxSize?: number;
  /** Whether to respect Cache-Control headers from server */
  respectCacheControl?: boolean;
  /** Whether to enable ETag support */
  enableETag?: boolean;
}

/**
 * Response Cache Service
 * Manages caching of GET request responses
 */
class ResponseCacheService {
  private cache: Map<string, CachedResponse> = new Map();
  private defaultTTL = 5 * 60 * 1000; // 5 minutes
  private maxSize = 100;
  private respectCacheControl = true;
  private enableETag = true;

  constructor(config: CacheConfig = {}) {
    this.defaultTTL = config.defaultTTL || this.defaultTTL;
    this.maxSize = config.maxSize || this.maxSize;
    this.respectCacheControl = config.respectCacheControl !== false;
    this.enableETag = config.enableETag !== false;
  }

  /**
   * Generate a unique key for a request
   */
  private generateCacheKey(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('&')
      : '';

    // Include Authorization header in key for user-specific cache
    const authHeader = config.headers?.Authorization || '';

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

  /**
   * Parse Cache-Control header
   */
  private parseCacheControl(header: string | undefined): {
    maxAge?: number;
    noCache?: boolean;
    noStore?: boolean;
    mustRevalidate?: boolean;
  } {
    if (!header) {
      return {};
    }

    const directives: Record<string, string | boolean> = {};
    const parts = header.split(',').map((p) => p.trim());

    for (const part of parts) {
      if (part.includes('=')) {
        const [key, value] = part.split('=').map((p) => p.trim());
        directives[key.toLowerCase()] = value;
      } else {
        directives[part.toLowerCase()] = true;
      }
    }

    return {
      maxAge: directives['max-age']
        ? parseInt(String(directives['max-age']), 10)
        : undefined,
      noCache: directives['no-cache'] === true,
      noStore: directives['no-store'] === true,
      mustRevalidate: directives['must-revalidate'] === true,
    };
  }

  /**
   * Check if a cached response is still valid
   */
  private isCacheValid(
    cached: CachedResponse,
    config: AxiosRequestConfig,
  ): boolean {
    const now = Date.now();
    const age = now - cached.timestamp;

    // Check if cache is expired based on maxAge
    if (cached.maxAge) {
      const maxAgeMs = cached.maxAge * 1000;
      if (age > maxAgeMs) {
        return false;
      }
    } else if (age > this.defaultTTL) {
      return false;
    }

    // Check ETag if enabled and request has If-None-Match
    if (this.enableETag && cached.etag) {
      const ifNoneMatch = config.headers?.['If-None-Match'];
      if (ifNoneMatch && ifNoneMatch !== cached.etag) {
        return false;
      }
    }

    // Check Last-Modified if enabled
    if (cached.lastModified) {
      const ifModifiedSince = config.headers?.['If-Modified-Since'];
      if (ifModifiedSince) {
        const cachedDate = new Date(cached.lastModified).getTime();
        const requestDate = new Date(ifModifiedSince).getTime();
        if (cachedDate < requestDate) {
          return false;
        }
      }
    }

    return true;
  }

  /**
   * Get cached response if available and valid
   */
  get<T = any>(config: AxiosRequestConfig): AxiosResponse<T> | null {
    // Only cache GET requests
    const method = (config.method || 'GET').toUpperCase();
    if (method !== 'GET') {
      return null;
    }

    // Check if caching is disabled for this request
    if ((config as any)?._disableCache === true) {
      return null;
    }

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

    if (!cached) {
      return null;
    }

    // Check if cache is still valid
    if (!this.isCacheValid(cached, config)) {
      this.cache.delete(key);
      logger.debug(`[ResponseCache] Cache expired: ${config.url}`);
      return null;
    }

    // Create AxiosResponse-like object from cache
    const response: AxiosResponse<T> = {
      data: cached.data as T,
      status: cached.status,
      statusText: cached.statusText,
      headers: cached.headers as any,
      config: config as InternalAxiosRequestConfig<any>,
      request: {},
    };

    logger.debug(`[ResponseCache] Cache hit: ${config.url}`, {
      key,
      age: Date.now() - cached.timestamp,
    });

    return response;
  }

  /**
   * Store response in cache
   */
  set<T = any>(config: AxiosRequestConfig, response: AxiosResponse<T>): void {
    // Only cache GET requests
    const method = (config.method || 'GET').toUpperCase();
    if (method !== 'GET') {
      return;
    }

    // Check if caching is disabled for this request
    if ((config as any)?._disableCache === true) {
      return;
    }

    // Check Cache-Control header
    const cacheControl =
      response.headers['cache-control'] || response.headers['Cache-Control'];
    const directives = this.parseCacheControl(cacheControl);

    // Don't cache if no-store or no-cache
    if (directives.noStore || directives.noCache) {
      logger.debug(
        `[ResponseCache] Not caching (no-store/no-cache): ${config.url}`,
      );
      return;
    }

    // Calculate max age
    let maxAge: number | undefined;
    if (this.respectCacheControl && directives.maxAge) {
      maxAge = directives.maxAge;
    } else {
      maxAge = Math.floor(this.defaultTTL / 1000); // Convert to seconds
    }

    // Extract ETag and Last-Modified
    const etag = response.headers['etag'] || response.headers['ETag'];
    const lastModified =
      response.headers['last-modified'] || response.headers['Last-Modified'];

    const key = this.generateCacheKey(config);

    // Check cache size limit
    if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
      // Remove oldest entry (simple FIFO)
      const firstKey = this.cache.keys().next().value;
      if (firstKey) {
        this.cache.delete(firstKey);
      }
    }

    // Store in cache
    this.cache.set(key, {
      data: response.data,
      headers: response.headers as Record<string, string>,
      status: response.status,
      statusText: response.statusText,
      timestamp: Date.now(),
      etag,
      lastModified,
      maxAge,
    });

    logger.debug(`[ResponseCache] Cached: ${config.url}`, {
      key,
      maxAge,
      etag: etag ? 'present' : 'none',
    });
  }

  /**
   * Invalidate cache for a specific URL pattern
   */
  invalidate(pattern: string | RegExp): number {
    let invalidated = 0;

    for (const key of this.cache.keys()) {
      const shouldInvalidate =
        typeof pattern === 'string' ? key.includes(pattern) : pattern.test(key);

      if (shouldInvalidate) {
        this.cache.delete(key);
        invalidated++;
      }
    }

    if (invalidated > 0) {
      logger.info(
        `[ResponseCache] Invalidated ${invalidated} cache entries for pattern: ${pattern}`,
      );
    }

    return invalidated;
  }

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

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

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

  /**
   * Clean up expired cache entries
   */
  cleanup(): number {
    const now = Date.now();
    let removed = 0;

    for (const [key, cached] of this.cache.entries()) {
      const age = now - cached.timestamp;
      const maxAgeMs =
        (cached.maxAge || Math.floor(this.defaultTTL / 1000)) * 1000;

      if (age > maxAgeMs) {
        this.cache.delete(key);
        removed++;
      }
    }

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

    return removed;
  }
}

// Singleton instance
export const responseCache = new ResponseCacheService({
  defaultTTL: 5 * 60 * 1000, // 5 minutes
  maxSize: 100,
  respectCacheControl: true,
  enableETag: true,
});

// Periodic cleanup (every minute)
if (typeof window !== 'undefined') {
  setInterval(() => {
    responseCache.cleanup();
  }, 60 * 1000);
}
