import { Inject, Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Logger } from "nestjs-pino";
import { CacheService } from "@bff/infra/cache/cache.service.js";
import type { OrderCreateResponse } from "@customer-portal/domain/orders";

/**
 * Order Idempotency Service
 *
 * Provides idempotency guarantees for order creation from checkout sessions.
 * Prevents duplicate orders from double-clicks or network retries by:
 * 1. Acquiring a lock before processing
 * 2. Storing the result after successful order creation
 * 3. Returning cached result for subsequent requests with same session ID
 *
 * Flow:
 * 1. Check for existing result (returns cached if found)
 * 2. Acquire lock (prevents concurrent processing)
 * 3. Create order
 * 4. Store result (for future retries)
 * 5. Delete checkout session (safe - result is cached)
 */
@Injectable()
export class OrderIdempotencyService {
  private readonly RESULT_PREFIX = "order-result:";
  private readonly LOCK_PREFIX = "order-lock:";
  private readonly RESULT_TTL_SECONDS: number;
  private readonly LOCK_TTL_SECONDS: number;

  constructor(
    private readonly cache: CacheService,
    @Inject(Logger) private readonly logger: Logger,
    config: ConfigService
  ) {
    this.RESULT_TTL_SECONDS = config.get<number>("ORDER_RESULT_TTL_SECONDS", 86400); // 24 hours
    this.LOCK_TTL_SECONDS = config.get<number>("ORDER_LOCK_TTL_SECONDS", 60); // 60 seconds for processing
  }

  /**
   * Check if an order was already created for this checkout session
   * @param sessionId - Checkout session ID
   * @returns Previously created order result, or null if not found
   */
  async getExistingResult(sessionId: string): Promise<OrderCreateResponse | null> {
    const key = this.buildResultKey(sessionId);
    const result = await this.cache.get<OrderCreateResponse>(key);

    if (result) {
      this.logger.log(
        { sessionId, orderId: result.sfOrderId },
        "Returning cached order result (idempotent replay)"
      );
    }

    return result;
  }

  /**
   * Acquire a processing lock for this checkout session
   * Prevents concurrent order creation attempts
   *
   * @param sessionId - Checkout session ID
   * @returns true if lock acquired, false if another process is already handling this session
   */
  async acquireLock(sessionId: string): Promise<boolean> {
    const key = this.buildLockKey(sessionId);
    const lockData = { timestamp: Date.now() };

    const acquired = await this.cache.setIfNotExists(key, lockData, this.LOCK_TTL_SECONDS);

    if (acquired) {
      this.logger.debug({ sessionId }, "Order creation lock acquired");
    } else {
      this.logger.warn({ sessionId }, "Order creation lock not acquired - concurrent processing");
    }

    return acquired;
  }

  /**
   * Store the order creation result for idempotent replays
   * Should be called BEFORE deleting the checkout session
   *
   * CRITICAL: If this fails, subsequent retries may create duplicate orders.
   * We re-throw the error to ensure the caller knows about this critical failure.
   *
   * @param sessionId - Checkout session ID
   * @param result - Order creation result to cache
   */
  async storeResult(sessionId: string, result: OrderCreateResponse): Promise<void> {
    const key = this.buildResultKey(sessionId);

    try {
      await this.cache.set(key, result, this.RESULT_TTL_SECONDS);

      this.logger.debug(
        { sessionId, orderId: result.sfOrderId },
        "Order result stored for idempotency"
      );
    } catch (error) {
      // CRITICAL: If we can't store the result, subsequent retries will create duplicates
      this.logger.error(
        { sessionId, orderId: result.sfOrderId, error },
        "CRITICAL: Failed to store order result for idempotency - retries may create duplicates"
      );
      // Re-throw to ensure the caller knows about this critical failure
      throw error;
    }
  }

  /**
   * Release the processing lock (optional - lock auto-expires)
   * Call this if order creation fails to allow immediate retry
   *
   * @param sessionId - Checkout session ID
   */
  async releaseLock(sessionId: string): Promise<void> {
    const key = this.buildLockKey(sessionId);
    await this.cache.del(key);
    this.logger.debug({ sessionId }, "Order creation lock released");
  }

  private buildResultKey(sessionId: string): string {
    return `${this.RESULT_PREFIX}${sessionId}`;
  }

  private buildLockKey(sessionId: string): string {
    return `${this.LOCK_PREFIX}${sessionId}`;
  }
}
