import { Injectable } from '@nestjs/common';
import { EntityManager, IsNull, Not } from 'typeorm';
import { ProgramRegistration, Program } from '../entities';
import { ApprovalStatusEnum } from '../enum/approval-status.enum';
import { RegistrationStatusEnum } from '../enum/registration-status.enum';
import { AppLoggerService } from './logger.service';
import {
  getRegistrationFlowConfig,
  determineFlowDecisions,
  canCompleteRegistration,
} from '../config/registration-flow.config';

/**
 * Service for handling registration sequence numbers and auto-approval logic
 */
@Injectable()
export class RegistrationSequenceService {

  constructor(
    private readonly logger: AppLoggerService,
  ) {}

  /**
   * Get next registration sequence number using integer column
   * Uses SELECT FOR UPDATE to handle race conditions
   */
  async getNextRegistrationSeqNumber(
    programId: number,
    sessionId: number | undefined,
    manager: EntityManager,
  ): Promise<number> {
    const whereCondition: any = { programRegistrationSeqNumber: Not(IsNull()) };
    if (sessionId) {
      whereCondition.programSessionId = sessionId;
    } else {
      whereCondition.programId = programId;
    }

    // No lock — same trade-off as hdb-be: reads current max inside the transaction,
    // then the caller writes with a WHERE seq IS NULL guard so the same registration
    // is never numbered twice. Rare same-number collisions across different concurrent
    // registrations are acceptable (seq is display-only, not a financial identifier).
    // Include soft-deleted rows (.withDeleted): a deleted registration's number must
    // stay "taken" so the next registration never reuses it (no duplicate seq numbers).
    const last = await manager
      .getRepository(ProgramRegistration)
      .createQueryBuilder('reg')
      .withDeleted()
      .where(whereCondition)
      .orderBy('reg.programRegistrationSeqNumber', 'DESC')
      .getOne();

    const nextNumber = (last?.programRegistrationSeqNumber ?? 0) + 1;
    this.logger.log(`Next registration sequence number: ${nextNumber} for program ${programId}`);
    return nextNumber;
  }

  async getNextWaitlistSeqNumber(
    programId: number,
    sessionId: number | undefined,
    manager: EntityManager,
  ): Promise<number> {
    const whereCondition: any = { waitingListSeqNumber: Not(IsNull()) };
    if (sessionId) {
      whereCondition.programSessionId = sessionId;
    } else {
      whereCondition.programId = programId;
    }

    // No lock — same trade-off as hdb-be: reads current max inside the transaction,
    // then the caller writes with a WHERE seq IS NULL guard so the same registration
    // is never numbered twice. Rare same-number collisions across different concurrent
    // registrations are acceptable (seq is display-only, not a financial identifier).
    // Include soft-deleted rows (.withDeleted): a deleted registration's number must
    // stay "taken" so the next registration never reuses it (no duplicate seq numbers).
    const last = await manager
      .getRepository(ProgramRegistration)
      .createQueryBuilder('reg')
      .withDeleted()
      .where(whereCondition)
      .orderBy('reg.waitingListSeqNumber', 'DESC')
      .getOne();

    const nextNumber = (last?.waitingListSeqNumber ?? 0) + 1;
    this.logger.log(`Next waitlist sequence number: ${nextNumber} for program ${programId}`);
    return nextNumber;
  }

  /**
   * Generate formatted sequence number from integer
   * Example: program.code = "HDB25", number = 1 → "HDB25-001"
   */
  generateFormattedSeqNumber(
    program: Program,
    numericSeq: number,
    isWaitlist: boolean = false,
  ): string {
    const prefix = program.code || `P${program.id}`;
    const wlPrefix = isWaitlist ? '-WL' : '';
    return `${prefix}${wlPrefix}-${numericSeq.toString().padStart(3, '0')}`;
  }

  /**
   * Determine if waitlist check should happen now or after approval
   */
  shouldCheckSeatsNow(program: Program): boolean {
    const flowConfig = getRegistrationFlowConfig(program, program.type);
    const flowDecisions = determineFlowDecisions(flowConfig);
    return flowDecisions.shouldCheckSeatsNow;
  }

  /**
   * Check if registration can be completed based on all requirements
   */
  async canCompleteRegistration(
    registrationId: number,
    program: Program,
    manager: EntityManager,
  ): Promise<boolean> {
    const flowConfig = getRegistrationFlowConfig(program, program.type);

    // Get registration with related data
    const registration = await manager.findOne(ProgramRegistration, {
      where: { id: registrationId },
      relations: ['paymentDetails', 'travelInfo', 'approvals'],
    });

    if (!registration) {
      return false;
    }

    // Check payment completion
    const paymentCompleted =
      !flowConfig.requiresPayment ||
      registration.paymentDetails?.some((p) => p.paymentStatus === 'online_completed') ||
      registration.isFreeSeat;


    // Check travel completion: both travelInfo and travelPlan must be completed if involvesTravel
    let travelCompleted = true;
    if (flowConfig.involvesTravel) {
      const travelInfoCompleted = registration.travelInfo?.some((t) => t.travelInfoStatus === 'completed');
      // Fetch travel plans for this registration
      const travelPlans = await manager.getRepository('RegistrationTravelPlan').find({ where: { registrationId } });
      const travelPlanCompleted = travelPlans?.some((tp: any) => tp.travelPlanStatus === 'completed');
      travelCompleted = !!travelInfoCompleted && !!travelPlanCompleted;
    }

    // Check approval completion
    const approvalCompleted =
      !flowConfig.requiresApproval ||
      registration.approvals?.some((a) => a.approvalStatus === ApprovalStatusEnum.APPROVED);

    return canCompleteRegistration(
      flowConfig,
      paymentCompleted,
      travelCompleted,
      approvalCompleted,
    );
  }

  /**
   * Update registration to COMPLETED if all requirements met
   */
  async updateToCompletedIfReady(
    registrationId: number,
    program: Program,
    manager: EntityManager,
  ): Promise<boolean> {
    const canComplete = await this.canCompleteRegistration(registrationId, program, manager);

    if (canComplete) {
      await manager.update(ProgramRegistration, registrationId, {
        registrationStatus: RegistrationStatusEnum.COMPLETED,
        auditRefId: registrationId,
        parentRefId: registrationId,
      });
      this.logger.log(`Registration ${registrationId} updated to COMPLETED`);
      return true;
    }

    return false;
  }

  /**
   * Update registration status with flexible transitions
   * Allows moving from any status to any other status (including COMPLETED to PENDING, ON_HOLD, etc.)
   */
  async updateRegistrationStatus(
    registrationId: number,
    newStatus: RegistrationStatusEnum,
    manager: EntityManager,
    reason?: string,
  ): Promise<void> {
    const registration = await manager.findOne(ProgramRegistration, {
      where: { id: registrationId },
    });

    if (!registration) {
      throw new Error(`Registration ${registrationId} not found`);
    }

    this.logger.log(
      `Updating registration ${registrationId} status from ${registration.registrationStatus} to ${newStatus}${reason ? ` - Reason: ${reason}` : ''}`,
    );

    await manager.update(ProgramRegistration, registrationId, {
      registrationStatus: newStatus,
      auditRefId: registrationId,
      parentRefId: registrationId,
    });
  }
}
