import { Injectable } from "@nestjs/common";
import { CacheService } from "@bff/infra/cache/cache.service";
import type { CacheBucketMetrics } from "@bff/infra/cache/cache.types";
import type { OrderDetails, OrderSummary } from "@customer-portal/domain/orders";

interface OrdersCacheMetrics {
  summaries: CacheBucketMetrics;
  details: CacheBucketMetrics;
  invalidations: number;
}

/**
 * Orders cache service
 * 
 * Uses CDC (Change Data Capture) for real-time cache invalidation.
 * No TTL - cache entries persist until explicitly invalidated by CDC events.
 * 
 * Features:
 * - Request coalescing: Prevents thundering herd on cache miss
 * - Granular invalidation: Account-level and order-level invalidation
 * - Metrics tracking: Monitors hits, misses, and invalidations
 */
@Injectable()
export class OrdersCacheService {
  private readonly metrics: OrdersCacheMetrics = {
    summaries: { hits: 0, misses: 0 },
    details: { hits: 0, misses: 0 },
    invalidations: 0,
  };

  // Request coalescing: Prevents duplicate API calls when multiple users
  // request the same data after CDC invalidation
  private readonly inflightRequests = new Map<string, Promise<unknown>>();

  constructor(private readonly cache: CacheService) {}

  /**
   * Get order summaries for an account (CDC-driven cache)
   */
  async getOrderSummaries(
    sfAccountId: string,
    fetcher: () => Promise<OrderSummary[]>
  ): Promise<OrderSummary[]> {
    const key = this.buildAccountKey(sfAccountId);
    return this.getOrSet<OrderSummary[]>("summaries", key, fetcher, false);
  }

  /**
   * Get order details by ID (CDC-driven cache)
   */
  async getOrderDetails(
    orderId: string,
    fetcher: () => Promise<OrderDetails | null>
  ): Promise<OrderDetails | null> {
    const key = this.buildOrderKey(orderId);
    return this.getOrSet<OrderDetails | null>("details", key, fetcher, true);
  }

  /**
   * Invalidate all cached orders for an account
   */
  async invalidateAccountOrders(sfAccountId: string): Promise<void> {
    const key = this.buildAccountKey(sfAccountId);
    this.metrics.invalidations++;
    await this.cache.del(key);
  }

  /**
   * Invalidate cached order details
   */
  async invalidateOrder(orderId: string): Promise<void> {
    const key = this.buildOrderKey(orderId);
    this.metrics.invalidations++;
    await this.cache.del(key);
  }

  /**
   * Get cache metrics for monitoring
   */
  getMetrics(): OrdersCacheMetrics {
    return {
      summaries: { ...this.metrics.summaries },
      details: { ...this.metrics.details },
      invalidations: this.metrics.invalidations,
    };
  }

  private async getOrSet<T>(
    bucket: keyof Pick<OrdersCacheMetrics, "summaries" | "details">,
    key: string,
    fetcher: () => Promise<T>,
    allowNull: boolean
  ): Promise<T> {
    // Check Redis cache first
    const cached = await this.cache.get<T>(key);
    
    if (cached !== null) {
      if (allowNull || cached !== null) {
        this.metrics[bucket].hits++;
        return cached;
      }
    }

    // Check for in-flight request (prevents thundering herd)
    const existingRequest = this.inflightRequests.get(key);
    if (existingRequest) {
      return existingRequest as Promise<T>;
    }

    // Fetch fresh data
    this.metrics[bucket].misses++;

    const fetchPromise = (async () => {
      try {
        const fresh = await fetcher();
        const valueToStore = allowNull ? (fresh ?? null) : fresh;
        await this.cache.set(key, valueToStore);
        return fresh;
      } finally {
        this.inflightRequests.delete(key);
      }
    })();

    this.inflightRequests.set(key, fetchPromise);
    return fetchPromise;
  }

  private buildAccountKey(sfAccountId: string): string {
    return `orders:account:${sfAccountId}`;
  }

  private buildOrderKey(orderId: string): string {
    return `orders:detail:${orderId}`;
  }
}
