import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { AppLoggerService } from 'src/common/services/logger.service';
import { ProgramSessionService } from 'src/program-session/program-session.service';
import { JoinLinkGenerationService } from './join-link-generation.service';
import {
  JOIN_LINK_GENERATION,
  JOIN_LINK_GENERATION_LOG,
} from 'src/common/constants/join-link-generation.constants';
import { isJoinLinkFallbackCronEnabled } from 'src/common/config/join-link-scheduler.config';

/**
 * Safety net for scheduled join-link generation. Every 4h it re-attempts
 * sessions whose AWS-scheduled event never completed — the schedule was never
 * registered (flag off / config missing), the event was dropped past its max
 * age, or a run crashed mid-flight. There is no DLQ; this cron is the backstop.
 *
 * Concurrency: each session is claimed atomically inside generateForSession, so
 * this sweep can never double-run a session that a live AWS event is handling.
 * The grace window on the candidate query further avoids even attempting a
 * just-fired event. Disabled by default (ENABLE_JOIN_LINK_FALLBACK_CRON).
 */
@Injectable()
export class JoinLinkGenerationFallbackService {
  constructor(
    private readonly programSessionService: ProgramSessionService,
    private readonly generationService: JoinLinkGenerationService,
    private readonly logger: AppLoggerService,
  ) {}

  @Cron(JOIN_LINK_GENERATION.FALLBACK_CRON)
  async sweep(): Promise<void> {
    if (!isJoinLinkFallbackCronEnabled()) {
      this.logger.log(JOIN_LINK_GENERATION_LOG.FALLBACK_DISABLED);
      return;
    }

    const now = Date.now();
    const dueBefore = new Date(now - JOIN_LINK_GENERATION.GRACE_MS);
    const staleBefore = new Date(now - JOIN_LINK_GENERATION.STALE_MS);

    try {
      const candidates = await this.programSessionService.findLinkGenerationCandidates(
        dueBefore,
        staleBefore,
        JOIN_LINK_GENERATION.FALLBACK_BATCH_LIMIT,
      );
      if (!candidates?.length) return;

      this.logger.log(JOIN_LINK_GENERATION_LOG.FALLBACK_FOUND(candidates.length));

      // Sequential — bound the fan-out of background bulk jobs. The atomic claim
      // inside generateForSession makes each attempt safe against a live event.
      for (const session of candidates) {
        try {
          await this.generationService.generateForSession(session.id, {
            actorUserId: session.updatedBy,
          });
        } catch (error) {
          // generateForSession already marked FAILED; keep sweeping the rest.
          this.logger.error(
            JOIN_LINK_GENERATION_LOG.FALLBACK_ITEM_FAILED(session.id),
            (error as Error)?.stack,
            { programSessionId: session.id, error: (error as Error)?.message },
          );
        }
      }
    } catch (error) {
      this.logger.error(JOIN_LINK_GENERATION_LOG.FALLBACK_SWEEP_FAILED, (error as Error)?.stack, {
        error: (error as Error)?.message,
      });
    }
  }
}
