/**
 * HTTP-level caching wrapper with ETags and Cache-Control headers
 * Implements WordPress REST API caching best practices
 */

import { CacheManager, CachePresets } from "./CacheManager.js";
import * as crypto from "crypto";
import { LoggerFactory } from "@/utils/logger.js";

export interface HttpCacheOptions {
  ttl?: number;
  cacheControl?: string;
  varyHeaders?: string[];
  private?: boolean;
  revalidate?: boolean;
}

export interface CachedResponse {
  data: unknown;
  status: number;
  headers: Record<string, string>;
  etag?: string;
  lastModified?: string;
  cacheControl?: string;
}

export interface RequestOptions {
  method: string;
  url: string;
  headers?: Record<string, string>;
  params?: Record<string, unknown>;
  data?: unknown;
}

/**
 * HTTP caching wrapper that adds intelligent caching to HTTP requests
 */
export class HttpCacheWrapper {
  private logger = LoggerFactory.cache();

  constructor(
    private cacheManager: CacheManager,
    private siteId: string,
  ) {}

  /**
   * Execute request with intelligent caching
   */
  async request<T = unknown>(
    requestFn: (extraHeaders?: Record<string, string>) => Promise<{
      data: T;
      status: number;
      headers: Record<string, string>;
    }>,
    options: RequestOptions,
    cacheOptions?: HttpCacheOptions,
  ): Promise<{
    data: T;
    status: number;
    headers: Record<string, string>;
    cached?: boolean;
  }> {
    // Only cache GET requests
    if (options.method.toUpperCase() !== "GET") {
      return await requestFn();
    }

    const cacheKey = this.generateCacheKey(options);
    const cachedEntry = this.cacheManager.getEntry(cacheKey);

    // Check for conditional request support
    if (cachedEntry && this.cacheManager.supportsConditionalRequest(cacheKey)) {
      const conditionalHeaders = this.cacheManager.getConditionalHeaders(cacheKey);

      // Add conditional headers to request
      const _requestWithHeaders = {
        ...options,
        headers: {
          ...options.headers,
          ...conditionalHeaders,
        },
      };

      try {
        const response = await requestFn(conditionalHeaders);

        // 304 Not Modified - return cached data
        if (response.status === 304) {
          return {
            data: (cachedEntry.value as CachedResponse).data as T,
            status: 200,
            headers: (cachedEntry.value as CachedResponse).headers,
            cached: true,
          };
        }

        // Content changed - update cache
        return await this.cacheAndReturn(
          response as { data: T; status: number; headers: Record<string, string> },
          cacheKey,
          cacheOptions,
        );
      } catch (_error) {
        // If conditional request fails, try without conditions
        this.logger.warn("Conditional request failed, falling back to regular request", {
          _error: _error instanceof Error ? _error.message : String(_error),
          siteId: this.siteId,
        });
      }
    }

    // Check for valid cached response
    const cached = this.cacheManager.get<CachedResponse>(cacheKey);
    if (cached) {
      return {
        data: cached.data as T,
        status: cached.status,
        headers: cached.headers,
        cached: true,
      };
    }

    // Execute fresh request
    const response = await requestFn();
    return await this.cacheAndReturn(response, cacheKey, cacheOptions);
  }

  /**
   * Invalidate cache for specific endpoint
   */
  invalidate(endpoint: string, params?: Record<string, unknown>): void {
    const cacheKey = this.cacheManager.generateKey(this.siteId, endpoint, params);
    this.cacheManager.delete(cacheKey);
  }

  /**
   * Invalidate cache entries matching pattern
   */
  invalidatePattern(pattern: string): number {
    const regex = new RegExp(`${this.siteId}:${pattern}`);
    return this.cacheManager.clearPattern(regex);
  }

  /**
   * Invalidate all cache for this site
   */
  invalidateAll(): number {
    return this.cacheManager.clearSite(this.siteId);
  }

  /**
   * Pre-warm cache with data
   */
  warm<T>(endpoint: string, data: T, params?: Record<string, unknown>, cacheOptions?: HttpCacheOptions): void {
    const cacheKey = this.cacheManager.generateKey(this.siteId, endpoint, params);
    const ttl = cacheOptions?.ttl || this.getDefaultTTL(endpoint);

    const cachedResponse: CachedResponse = {
      data,
      status: 200,
      headers: this.generateCacheHeaders(cacheOptions, endpoint),
      etag: this.generateETag(data),
      lastModified: new Date().toUTCString(),
      cacheControl: cacheOptions?.cacheControl || this.getDefaultCacheControl(endpoint),
    };

    this.cacheManager.set(cacheKey, cachedResponse, ttl, cachedResponse.etag, cachedResponse.lastModified);
  }

  /**
   * Get cache statistics for this site
   */
  getStats() {
    return this.cacheManager.getStats();
  }

  /**
   * Generate cache key for request
   */
  private generateCacheKey(options: RequestOptions): string {
    const endpoint = this.extractEndpoint(options.url);
    return this.cacheManager.generateKey(this.siteId, endpoint, {
      ...options.params,
      // Include relevant headers that affect response
      ...this.extractCacheableHeaders(options.headers),
    });
  }

  /**
   * Extract endpoint from full URL
   */
  private extractEndpoint(url: string): string {
    // Extract the path after /wp-json/wp/v2/
    const match = url.match(/\/wp-json\/wp\/v2\/(.+?)(?:\?|$)/);
    return match ? match[1] : url;
  }

  /**
   * Extract headers that affect caching
   */
  private extractCacheableHeaders(headers?: Record<string, string>): Record<string, string> {
    if (!headers) return {};

    const cacheableHeaders: Record<string, string> = {};
    const relevantHeaders = ["accept", "accept-language", "authorization"];

    for (const header of relevantHeaders) {
      if (headers[header]) {
        cacheableHeaders[header] = headers[header];
      }
    }

    return cacheableHeaders;
  }

  /**
   * Execute request with modified headers
   */
  /**
   * Cache response and return with cache metadata
   */
  private async cacheAndReturn<T>(
    response: { data: T; status: number; headers: Record<string, string> },
    cacheKey: string,
    cacheOptions?: HttpCacheOptions,
  ): Promise<{
    data: T;
    status: number;
    headers: Record<string, string>;
    cached?: boolean;
  }> {
    // Don't cache error responses (unless specifically configured)
    if (response.status >= 400) {
      return response;
    }

    const endpoint = this.extractEndpointFromKey(cacheKey);
    const ttl = cacheOptions?.ttl || this.getDefaultTTL(endpoint);

    // Generate ETags and cache headers
    const etag = this.generateETag(response.data);
    const lastModified = new Date().toUTCString();
    const cacheControl = cacheOptions?.cacheControl || this.getDefaultCacheControl(endpoint);

    const cachedResponse: CachedResponse = {
      data: response.data,
      status: response.status,
      headers: {
        ...response.headers,
        etag: etag,
        "last-modified": lastModified,
        "cache-control": cacheControl,
      },
      etag,
      lastModified,
      cacheControl,
    };

    // Store in cache
    this.cacheManager.set(cacheKey, cachedResponse, ttl, etag, lastModified);

    return {
      data: response.data,
      status: response.status,
      headers: cachedResponse.headers,
      cached: false,
    };
  }

  /**
   * Generate ETag for response data
   */
  private generateETag(data: unknown): string {
    const hash = crypto.createHash("md5").update(JSON.stringify(data)).digest("hex");
    return `"${hash}"`;
  }

  /**
   * Get default TTL based on endpoint type
   */
  private getDefaultTTL(endpoint: string): number {
    // Static data endpoints
    if (this.isStaticEndpoint(endpoint)) {
      return CachePresets.STATIC.ttl;
    }

    // Semi-static data endpoints
    if (this.isSemiStaticEndpoint(endpoint)) {
      return CachePresets.SEMI_STATIC.ttl;
    }

    // Session/auth endpoints
    if (this.isSessionEndpoint(endpoint)) {
      return CachePresets.SESSION.ttl;
    }

    // Default to dynamic for posts, comments, etc.
    return CachePresets.DYNAMIC.ttl;
  }

  /**
   * Get default Cache-Control header based on endpoint
   */
  private getDefaultCacheControl(endpoint: string): string {
    if (this.isStaticEndpoint(endpoint)) {
      return CachePresets.STATIC.cacheControl;
    }

    if (this.isSemiStaticEndpoint(endpoint)) {
      return CachePresets.SEMI_STATIC.cacheControl;
    }

    if (this.isSessionEndpoint(endpoint)) {
      return CachePresets.SESSION.cacheControl;
    }

    return CachePresets.DYNAMIC.cacheControl;
  }

  /**
   * Generate cache headers
   */
  private generateCacheHeaders(options?: HttpCacheOptions, endpoint?: string): Record<string, string> {
    const headers: Record<string, string> = {};

    if (options?.cacheControl) {
      headers["cache-control"] = options.cacheControl;
    } else if (endpoint) {
      headers["cache-control"] = this.getDefaultCacheControl(endpoint);
    }

    if (options?.varyHeaders?.length) {
      headers["vary"] = options.varyHeaders.join(", ");
    }

    return headers;
  }

  /**
   * Check if endpoint contains static data
   */
  private isStaticEndpoint(endpoint: string): boolean {
    const staticEndpoints = ["settings", "types", "statuses"];
    return staticEndpoints.some((pattern) => endpoint.includes(pattern));
  }

  /**
   * Check if endpoint contains semi-static data
   */
  private isSemiStaticEndpoint(endpoint: string): boolean {
    const semiStaticEndpoints = ["categories", "tags", "users", "taxonomies"];
    return semiStaticEndpoints.some((pattern) => endpoint.includes(pattern));
  }

  /**
   * Check if endpoint is session-related
   */
  private isSessionEndpoint(endpoint: string): boolean {
    const sessionEndpoints = ["users/me", "application-passwords"];
    return sessionEndpoints.some((pattern) => endpoint.includes(pattern));
  }

  /**
   * Extract endpoint from cache key
   */
  private extractEndpointFromKey(cacheKey: string): string {
    const parts = cacheKey.split(":");
    return parts[1] || "";
  }
}
