import { Injectable } from '@nestjs/common';
import { EventBus } from '@nestjs/cqrs';
import { Cron, CronExpression } from '@nestjs/schedule';
import { ListingStatus } from '@prisma/client';
import { PrismaService, LoggerService } from '@modules/shared';
import { ListingExpiringEvent } from '../../domain/events/listing-expiring.event';

/**
 * Daily cron that fires a 3-day expiry warning for active listings.
 *
 * Design notes:
 * - Runs once per day at 08:00 (Vietnam time, UTC+7 → 01:00 UTC).
 * - Queries listings whose `expiresAt` falls within the next 1–3 days
 *   AND whose `expiryNotifiedAt` is still NULL (idempotent guard).
 * - Uses a single atomic SQL UPDATE … RETURNING so concurrent instances
 *   cannot double-fire: only the first writer will satisfy the NULL predicate.
 * - Publishes `ListingExpiringEvent` per affected listing so the
 *   notifications module can dispatch Zalo OA / email messages.
 */
@Injectable()
export class ListingExpiryCronService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly eventBus: EventBus,
    private readonly logger: LoggerService,
  ) {}

  @Cron('0 1 * * *', { name: 'listing-expiry-warning', timeZone: 'UTC' })
  async notifyExpiringListings(): Promise<void> {
    const now = new Date();
    const in3Days = new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000);

    try {
      // Atomically claim rows: set expiryNotifiedAt so concurrent instances skip them.
      const expiring = await this.prisma.$queryRaw<
        Array<{ id: string; sellerId: string; expiresAt: Date }>
      >`
        UPDATE "Listing"
        SET "expiryNotifiedAt" = NOW(),
            "updatedAt"        = NOW()
        WHERE status            = ${ListingStatus.ACTIVE}::"ListingStatus"
          AND "expiresAt"       IS NOT NULL
          AND "expiresAt"       > ${now}
          AND "expiresAt"       <= ${in3Days}
          AND "expiryNotifiedAt" IS NULL
        RETURNING id, "sellerId", "expiresAt"
      `;

      if (expiring.length === 0) {
        this.logger.debug(
          'No listings expiring in the next 3 days — nothing to notify',
          'ListingExpiryCronService',
        );
        return;
      }

      for (const row of expiring) {
        this.eventBus.publish(
          new ListingExpiringEvent(row.id, row.sellerId, row.expiresAt),
        );
      }

      this.logger.log(
        `Sent expiry-warning events for ${expiring.length} listing(s) at ${now.toISOString()}`,
        'ListingExpiryCronService',
      );
    } catch (err) {
      this.logger.error(
        `Failed to send listing expiry warnings: ${(err as Error).message}`,
        (err as Error).stack,
        'ListingExpiryCronService',
      );
    }
  }
}
