import { Logger } from '@nestjs/common';
import { EntityManager, OptimisticLockVersionMismatchError } from 'typeorm';

const logger = new Logger('OptimisticLock');

/**
 * Optimistic Lock retry wrapper.
 * Retries the operation when a version conflict is detected.
 *
 * Critical for financial operations:
 * - Wallet balance updates (prevent double-spending)
 * - Order status transitions
 * - Coupon inventory (prevent overselling)
 * - Settlement records
 *
 * Usage:
 *   await withOptimisticLock(manager, 3, async (mgr) => {
 *     const wallet = await mgr.findOne(Wallet, { where: { id }, lock: { mode: 'optimistic', version } });
 *     wallet.balance = wallet.balance.minus(amount);
 *     await mgr.save(wallet);
 *   });
 */
export async function withOptimisticLock<T>(
  manager: EntityManager,
  maxRetries: number,
  operation: (manager: EntityManager) => Promise<T>,
): Promise<T> {
  let attempt = 0;

  while (attempt <= maxRetries) {
    try {
      return await manager.transaction(async (txManager) => {
        return await operation(txManager);
      });
    } catch (error) {
      if (
        error instanceof OptimisticLockVersionMismatchError ||
        error.message?.includes('version')
      ) {
        attempt++;
        if (attempt > maxRetries) {
          logger.error(
            `Optimistic lock failed after ${maxRetries} retries: ${error.message}`,
          );
          throw error;
        }
        logger.warn(
          `Optimistic lock conflict, retry ${attempt}/${maxRetries}`,
        );
        // Exponential backoff: 50ms, 100ms, 200ms...
        await new Promise((r) => setTimeout(r, 50 * Math.pow(2, attempt - 1)));
      } else {
        throw error;
      }
    }
  }

  throw new Error('Optimistic lock: unreachable');
}

/**
 * Pessimistic lock helper for critical inventory operations.
 * Uses SELECT ... FOR UPDATE to serialize access.
 *
 * Usage (coupon inventory):
 *   await withPessimisticLock(manager, Coupon, couponId, async (coupon, mgr) => {
 *     if (coupon.remainingQuantity <= 0) throw new Error('Sold out');
 *     coupon.remainingQuantity -= 1;
 *     await mgr.save(coupon);
 *   });
 */
export async function withPessimisticLock<Entity extends { id: string }>(
  manager: EntityManager,
  entityClass: new () => Entity,
  entityId: string,
  operation: (entity: Entity, manager: EntityManager) => Promise<void>,
): Promise<void> {
  await manager.transaction(async (txManager) => {
    const entity = await txManager.findOne(entityClass as any, {
      where: { id: entityId } as any,
      lock: { mode: 'pessimistic_write' },
    });

    if (!entity) {
      throw new Error(
        `${entityClass.name} with id ${entityId} not found`,
      );
    }

    await operation(entity as Entity, txManager);
  });
}
