import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { ModuleRef } from '@nestjs/core';
import { PrismaService } from '../../common/prisma.service';
import { PaymentClientService } from './payment-client.service';
import { PaymentsService } from './payments.service';
import { PaymentReferenceType, ProviderPaymentStatus } from '@edr/types';

// PaymentsService is request-scoped (AuditService.@Inject(REQUEST) bubbles up).
// This service keeps only singleton deps so its @Cron method registers correctly,
// then resolves PaymentsService per-tick via ModuleRef (same pattern as
// PaymentEventsConsumer).
@Injectable()
export class PaymentSyncService {
  private readonly logger = new Logger(PaymentSyncService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly paymentClient: PaymentClientService,
    private readonly moduleRef: ModuleRef,
  ) {}

  // ─────────────────────────────────────────────────────────────────────────
  // Every 1 min: poll the payment service for any PENDING_PAYMENT bookings
  // whose payment intent has moved to SUCCEEDED on the gateway but whose
  // confirmation event was never delivered (missed RabbitMQ message, network
  // blip, etc.).  finalizePaymentSuccess() is fully idempotent so re-running
  // it for an already-confirmed booking is safe.
  //
  // Processes at most 50 bookings per cycle to avoid hammering the payment
  // service; the next tick picks up the remainder.
  // ─────────────────────────────────────────────────────────────────────────
  @Cron('*/1 * * * *')
  async syncPaymentStatuses() {
    const BATCH_SIZE = 50;

    const bookings = await this.prisma.booking.findMany({
      where: {
        status: 'PENDING_PAYMENT',
        paymentIntent: { status: { in: ['REQUIRES_ACTION', 'PROCESSING'] } },
      },
      include: { paymentIntent: true },
      take: BATCH_SIZE,
      orderBy: { createdAt: 'asc' },
    });

    if (bookings.length === 0) return;

    let confirmed = 0;
    let failed    = 0;
    let errored   = 0;

    // resolve() (not get()) because PaymentsService is scoped — same pattern
    // as PaymentEventsConsumer.
    const paymentsService = await this.moduleRef.resolve(
      PaymentsService,
      undefined,
      { strict: false },
    );

    for (const booking of bookings) {
      if (!booking.paymentIntent) continue;

      try {
        const snapshot = await this.paymentClient.getIntentByReference(
          PaymentReferenceType.BOOKING,
          booking.id,
        );

        if (!snapshot) continue;

        if (snapshot.status === ProviderPaymentStatus.SUCCEEDED) {
          const result = await paymentsService.finalizePaymentSuccess({
            intentId:      booking.paymentIntent.id,
            providerTxnId: snapshot.providerTxnId,
            paidAt:        snapshot.paidAt ? new Date(snapshot.paidAt) : undefined,
          });
          if (!result.alreadyFinalized) {
            this.logger.log(`Payment sync confirmed: ${booking.bookingRef}`);
            confirmed++;
          }
        } else if (
          snapshot.status === ProviderPaymentStatus.FAILED ||
          snapshot.status === ProviderPaymentStatus.CANCELLED
        ) {
          this.logger.warn(
            `Payment sync: ${booking.bookingRef} intent is ${snapshot.status} — ` +
            `booking will be auto-cancelled at payment deadline`,
          );
          failed++;
        }
        // REQUIRES_ACTION / PROCESSING → still pending, retry next cycle
      } catch (err) {
        this.logger.error(
          `Payment sync error for ${booking.bookingRef}: ` +
          `${err instanceof Error ? err.message : String(err)}`,
        );
        errored++;
      }
    }

    if (confirmed > 0 || failed > 0 || errored > 0) {
      this.logger.log(
        `Payment sync run: ${bookings.length} checked, ` +
        `${confirmed} confirmed, ${failed} failed/cancelled, ${errored} errors`,
      );
    }
  }
}
