/**
 * Registration Status Manager Service
 * Centralized service for managing and validating registration status changes
 * 
 * Features:
 * - Program-type-specific status flows (HDB, MSD, TAT, ENTRAINMENT)
 * - Role-based status transition permissions
 * - Automatic status determination based on requirements completion
 * - Single source of truth for all status updates
 */

import { Injectable, Logger } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { ProgramRegistration, RegistrationApproval, RegistrationPaymentDetail, RegistrationTravelInfo, RegistrationTravelPlan } from '../entities';
import { RegistrationStatusEnum } from '../enum/registration-status.enum';
import { ApprovalStatusEnum } from '../enum/approval-status.enum';
import InifniBadRequestException from '../exceptions/infini-badrequest-exception';
import { ERROR_CODES } from '../constants/error-string-constants';
import {
  getRegistrationFlowConfig,
  determineStatusAfterPayment,
  determineStatusAfterApproval,
  determineStatusAfterTravel,
  determineRegistrationStatus,
  canCompleteRegistrationFromDetails,
  isStatusTransitionAllowed,
  RegistrationFlowConfig,
} from '../config/registration-flow.config';

/**
 * Status transition trigger types
 */
export type StatusTrigger = 'payment' | 'approval' | 'travel' | 'submission' | 'cancellation' | 'hold' | 'manual' | 'rejected' | 'save_as_draft';

/**
 * Role-based permission configuration for status transitions
 */
interface RolePermissions {
  canCancel: boolean;
  canHold: boolean;
  canReject: boolean;
  canReactivate: boolean;
  canManualOverride: boolean;
}

/**
 * Status transition result
 */
export interface StatusTransitionResult {
  oldStatus: RegistrationStatusEnum;
  newStatus: RegistrationStatusEnum;
  isChanged: boolean;
  reason?: string;
  missingRequirements?: string[];
}

@Injectable()
export class RegistrationStatusManagerService {
  private readonly logger = new Logger(RegistrationStatusManagerService.name);

  /**
   * Role-based permissions matrix
   * Defines what each role can do with registration statuses
   */
  private readonly rolePermissions: Record<string, RolePermissions> = {
    admin: {
      canCancel: true,
      canHold: true,
      canReject: true,
      canReactivate: true,
      canManualOverride: true,
    },
    mahatria: {
      canCancel: true,
      canHold: true,
      canReject: true,
      canReactivate: true,
      canManualOverride: true,
    },
    shoba: {
      canCancel: true,
      canHold: true,
      canReject: true,
      canReactivate: false,
      canManualOverride: false,
    },
    rm: {
      canCancel: false,
      canHold: true,
      canReject: true,
      canReactivate: false,
      canManualOverride: false,
    },
    relational_manager: {
      canCancel: false,
      canHold: true,
      canReject: true,
      canReactivate: false,
      canManualOverride: false,
    },
    operational_manger: {
      canCancel: true,
      canHold: true,
      canReject: false,
      canReactivate: false,
      canManualOverride: false,
    },
    rm_support: {
      canCancel: false,
      canHold: false,
      canReject: false,
      canReactivate: false,
      canManualOverride: false,
    },
    finance_manager: {
      canCancel: false,
      canHold: false,
      canReject: false,
      canReactivate: false,
      canManualOverride: false,
    },
    viewer: {
      canCancel: false,
      canHold: false,
      canReject: false,
      canReactivate: false,
      canManualOverride: false,
    },
  };

  /**
   * Get role permissions for a user
   */
  private getRolePermissions(userRoles: string[]): RolePermissions {
    // Check for highest privilege role
    const priorityRoles = ['admin', 'mahatria', 'shoba', 'rm', 'relational_manager', 'operational_manger', 'rm_support', 'finance_manager', 'viewer'];
    
    for (const role of priorityRoles) {
      if (userRoles.includes(role)) {
        return this.rolePermissions[role];
      }
    }
    
    // Default to viewer permissions
    return this.rolePermissions.viewer;
  }

  /**
   * Validate if a user has permission for a specific status transition
   */
  validateTransitionPermission(
    fromStatus: RegistrationStatusEnum,
    toStatus: RegistrationStatusEnum,
    userRoles: string[],
  ): { isValid: boolean; reason?: string } {
    const permissions = this.getRolePermissions(userRoles);

    // Check cancellation permission
    if (toStatus === RegistrationStatusEnum.CANCELLED) {
      if (!permissions.canCancel) {
        return {
          isValid: false,
          reason: 'User does not have permission to cancel registrations',
        };
      }
    }

    // Check hold permission
    if (toStatus === RegistrationStatusEnum.ON_HOLD) {
      if (!permissions.canHold) {
        return {
          isValid: false,
          reason: 'User does not have permission to put registrations on hold',
        };
      }
    }

    // Check rejection permission
    if (toStatus === RegistrationStatusEnum.REJECTED) {
      if (!permissions.canReject) {
        return {
          isValid: false,
          reason: 'User does not have permission to reject registrations',
        };
      }
    }

    // Check reactivation permission (CANCELLED/REJECTED -> other status)
    if (
      [RegistrationStatusEnum.CANCELLED, RegistrationStatusEnum.REJECTED].includes(fromStatus) &&
      ![RegistrationStatusEnum.CANCELLED, RegistrationStatusEnum.REJECTED].includes(toStatus)
    ) {
      if (!permissions.canReactivate) {
        return {
          isValid: false,
          reason: 'User does not have permission to reactivate cancelled/rejected registrations',
        };
      }
    }

    return { isValid: true };
  }

  /**
   * Automatically determine and update registration status based on current state
   * This is the ONE central place that decides and updates registration status
   * 
   * @param registrationId Registration ID
   * @param trigger What triggered the status check
   * @param manager EntityManager for transaction
   * @param userId User making the change
   * @param userRoles User's roles for permission checking
   * @returns Status transition result
   */
  async updateRegistrationStatus(
    registrationId: number,
    trigger: StatusTrigger,
    manager: EntityManager,
    userId?: number,
    userRoles?: string[],
  ): Promise<StatusTransitionResult> {
    this.logger.log(`[STATUS UPDATE] Registration ${registrationId}, Trigger: ${trigger}`);

    // Load registration with all necessary relations
    const registration = await manager.findOne(ProgramRegistration, {
      where: { id: registrationId },
      relations: [
        'program',
        'program.type',
        'paymentDetails',
        'approvals',
        'travelInfo',
        'travelPlans',
      ],
    });

    if (!registration) {
      throw new InifniBadRequestException(
        ERROR_CODES.REGISTRATION_NOT_FOUND,
        null,
        null,
        registrationId.toString(),
      );
    }

    const oldStatus = registration.registrationStatus;
    const config = getRegistrationFlowConfig(registration.program, registration.program.type);

    let newStatus: string;

    // Determine new status based on trigger
    switch (trigger) {
      case 'payment':
        newStatus = determineStatusAfterPayment(config, registration);
        break;

      case 'approval':
        newStatus = determineStatusAfterApproval(config, registration);
        break;

      case 'travel':
        newStatus = determineStatusAfterTravel(config, registration);
        break;

      case 'submission':
        newStatus = this.determineStatusAfterSubmission(config, registration);
        break;

      case 'cancellation':
        newStatus = RegistrationStatusEnum.CANCELLED;
        break;

      case 'rejected':
        newStatus = RegistrationStatusEnum.REJECTED;
        break;
      
      case 'hold':
        newStatus = RegistrationStatusEnum.PENDING;
        break;
      
      case 'save_as_draft':
        newStatus = RegistrationStatusEnum.SAVE_AS_DRAFT;
        break;

      case 'manual':
        // Manual override - determine most appropriate status
        newStatus = determineRegistrationStatus(config, registration);
        break;

      default:
        newStatus = determineRegistrationStatus(config, registration);
    }

    // Check if status actually changed
    if (oldStatus === newStatus) {
      this.logger.log(`[STATUS UPDATE] No change needed: ${oldStatus}`);
      return {
        oldStatus,
        newStatus: newStatus as RegistrationStatusEnum,
        isChanged: false,
        reason: 'Status already matches determined status',
      };
    }

    // Validate transition permission if user roles provided
    if (userRoles && userRoles.length > 0) {
      const permissionCheck = this.validateTransitionPermission(
        oldStatus,
        newStatus as RegistrationStatusEnum,
        userRoles,
      );
      
      if (!permissionCheck.isValid) {
        throw new InifniBadRequestException(
          ERROR_CODES.REGISTRATION_INVALID_STATUS,
          null,
          null,
          permissionCheck.reason || 'Permission denied for status transition',
        );
      }
    }

    // Update status
    await manager.update(ProgramRegistration, registrationId, {
      registrationStatus: newStatus as RegistrationStatusEnum,
      updatedAt: new Date(),
      ...(userId && { updatedBy: { id: userId } as any }),
    });

    this.logger.log(`[STATUS UPDATE] Changed: ${oldStatus} → ${newStatus}`);

    return {
      oldStatus,
      newStatus: newStatus as RegistrationStatusEnum,
      isChanged: true,
      reason: `Status updated based on ${trigger}`,
    };
  }

  /**
   * Determine status after initial submission
   * Program-type-specific logic for submission
   * Note: Approval status is tracked separately in RegistrationApproval table
   */
  private determineStatusAfterSubmission(
    config: RegistrationFlowConfig,
    registration: any,
  ): string {
    const programTypeKey = registration.program?.type?.key;

    this.logger.log(`[SUBMISSION] Program Type: ${programTypeKey}, Config:`, config);

    // All program types: Check if all requirements can be met immediately
    if (canCompleteRegistrationFromDetails(config, registration)) {
      return RegistrationStatusEnum.COMPLETED;
    }

    // If any requirements are pending (payment, travel, approval)
    // Status stays as PENDING - actual approval status is tracked in RegistrationApproval table
    return RegistrationStatusEnum.PENDING;
  }

  /**
   * Get missing requirements for a registration
   */
  async getMissingRequirements(
    registrationId: number,
    manager: EntityManager,
  ): Promise<string[]> {
    const registration = await manager.findOne(ProgramRegistration, {
      where: { id: registrationId },
      relations: [
        'program',
        'program.type',
        'paymentDetails',
        'approvals',
        'travelInfo',
        'travelPlans',
      ],
    });

    if (!registration) {
      throw new InifniBadRequestException(
        ERROR_CODES.REGISTRATION_NOT_FOUND,
        null,
        null,
        registrationId.toString(),
      );
    }

    const config = getRegistrationFlowConfig(registration.program, registration.program.type);
    const missing: string[] = [];

    // Check approval
    if (config.requiresApproval) {
      const hasApproval = registration.approvals?.some(
        (a: any) => a.approvalStatus === ApprovalStatusEnum.APPROVED && a.deletedAt === null,
      );
      if (!hasApproval) {
        missing.push('Approval');
      }
    }

    // Check payment
    if (config.requiresPayment) {
      const hasPayment =
        registration.isFreeSeat ||
        registration.paymentDetails?.some(
          (p: any) =>
            p.paymentStatus === 'online_completed' || p.paymentStatus === 'offline_completed',
        );
      if (!hasPayment) {
        missing.push('Payment');
      }
    }

    // Check travel
    if (config.involvesTravel) {
      const hasTravelInfo = registration.travelInfo?.some(
        (t: any) => t.travelInfoStatus === 'completed',
      );
      const hasTravelPlan = registration.travelPlans?.some(
        (tp: any) => tp.travelPlanStatus === 'completed',
      );
      if (!hasTravelInfo || !hasTravelPlan) {
        missing.push('Travel Details');
      }
    }

    return missing;
  }

  /**
   * Check if a registration can be completed
   */
  async canComplete(
    registrationId: number,
    manager: EntityManager,
  ): Promise<{ canComplete: boolean; missingRequirements: string[] }> {
    const missing = await this.getMissingRequirements(registrationId, manager);
    return {
      canComplete: missing.length === 0,
      missingRequirements: missing,
    };
  }

  /**
   * Get flow configuration for a registration
   */
  async getFlowConfig(
    registrationId: number,
    manager: EntityManager,
  ): Promise<RegistrationFlowConfig> {
    const registration = await manager.findOne(ProgramRegistration, {
      where: { id: registrationId },
      relations: ['program', 'program.type'],
    });

    if (!registration) {
      throw new InifniBadRequestException(
        ERROR_CODES.REGISTRATION_NOT_FOUND,
        null,
        null,
        registrationId.toString(),
      );
    }

    return getRegistrationFlowConfig(registration.program, registration.program.type);
  }

  /**
   * Manually set status with validation (admin override)
   */
  async setStatus(
    registrationId: number,
    targetStatus: RegistrationStatusEnum,
    manager: EntityManager,
    userId: number,
    userRoles: string[],
    reason?: string,
  ): Promise<StatusTransitionResult> {
    const permissions = this.getRolePermissions(userRoles);

    if (!permissions.canManualOverride) {
      throw new InifniBadRequestException(
        ERROR_CODES.REGISTRATION_INVALID_STATUS,
        null,
        null,
        'User does not have permission for manual status override',
      );
    }

    const registration = await manager.findOne(ProgramRegistration, {
      where: { id: registrationId },
    });

    if (!registration) {
      throw new InifniBadRequestException(
        ERROR_CODES.REGISTRATION_NOT_FOUND,
        null,
        null,
        registrationId.toString(),
      );
    }

    const oldStatus = registration.registrationStatus;

    // Validate transition is allowed
    const transitionCheck = isStatusTransitionAllowed(oldStatus, targetStatus);
    if (!transitionCheck.isValid) {
      throw new InifniBadRequestException(
        ERROR_CODES.REGISTRATION_INVALID_STATUS,
        null,
        null,
        transitionCheck.reason || 'Status transition not allowed',
      );
    }

    // Validate permission
    const permissionCheck = this.validateTransitionPermission(
      oldStatus,
      targetStatus,
      userRoles,
    );

    if (!permissionCheck.isValid) {
      throw new InifniBadRequestException(
        ERROR_CODES.REGISTRATION_INVALID_STATUS,
        null,
        null,
        permissionCheck.reason || 'Permission denied for status transition',
      );
    }

    await manager.update(ProgramRegistration, registrationId, {
      registrationStatus: targetStatus,
      updatedAt: new Date(),
      updatedBy: { id: userId } as any,
    });

    this.logger.log(
      `[MANUAL STATUS] Registration ${registrationId}: ${oldStatus} → ${targetStatus} by user ${userId}. Reason: ${reason || 'No reason provided'}`,
    );

    return {
      oldStatus,
      newStatus: targetStatus,
      isChanged: oldStatus !== targetStatus,
      reason: reason || 'Manual status override',
    };
  }

  /**
   * Get allowed next statuses for a registration based on current state and user role
   */
  async getAllowedNextStatuses(
    registrationId: number,
    userRoles: string[],
    manager: EntityManager,
  ): Promise<RegistrationStatusEnum[]> {
    const registration = await manager.findOne(ProgramRegistration, {
      where: { id: registrationId },
    });

    if (!registration) {
      throw new InifniBadRequestException(
        ERROR_CODES.REGISTRATION_NOT_FOUND,
        null,
        null,
        registrationId.toString(),
      );
    }

    const currentStatus = registration.registrationStatus;
    const allowedStatuses: RegistrationStatusEnum[] = [];

    // All statuses are technically allowed (flexible system), but filter by permissions
    const allStatuses = Object.values(RegistrationStatusEnum);

    for (const status of allStatuses) {
      if (status === currentStatus) continue; // Skip current status

      const permissionCheck = this.validateTransitionPermission(
        currentStatus,
        status,
        userRoles,
      );

      if (permissionCheck.isValid) {
        allowedStatuses.push(status);
      }
    }

    return allowedStatuses;
  }

  /**
   * Update payment status and sync registration status
   * @param paymentId Payment detail ID
   * @param newStatus New payment status
   * @param manager EntityManager
   * @param userId User making the change
   */
  async updatePaymentStatus(
    paymentId: number,
    newStatus: string,
    manager: EntityManager,
    userId?: number,
  ): Promise<void> {
    const payment = await manager.findOne(RegistrationPaymentDetail, {
      where: { id: paymentId },
      relations: ['registration'],
    });

    if (!payment) {
      throw new InifniBadRequestException(
        ERROR_CODES.PAYMENT_NOTFOUND,
        null,
        null,
        `Payment ${paymentId} not found`,
      );
    }

    // Update payment status
    await manager.update(RegistrationPaymentDetail, paymentId, {
      paymentStatus: newStatus as any,
      updatedAt: new Date(),
      ...(userId && { updatedBy: { id: userId } as any }),
    });

    this.logger.log(`[PAYMENT STATUS] Payment ${paymentId} updated to ${newStatus}`);

    // If payment is completed, trigger registration status update
    if (newStatus === 'online_completed' || newStatus === 'offline_completed') {
      await this.updateRegistrationStatus(
        payment.registration.id,
        'payment',
        manager,
        userId,
      );
    }
  }

  /**
   * Update travel info status and sync registration status
   * @param travelInfoId Travel info ID
   * @param newStatus New travel status
   * @param manager EntityManager
   * @param userId User making the change
   */
  async updateTravelInfoStatus(
    travelInfoId: number,
    newStatus: string,
    manager: EntityManager,
    userId?: number,
  ): Promise<void> {
    const travelInfo = await manager.findOne(RegistrationTravelInfo, {
      where: { id: travelInfoId },
      relations: ['registration'],
    });

    if (!travelInfo) {
      throw new InifniBadRequestException(
        ERROR_CODES.REGISTRATION_NOT_FOUND,
        null,
        null,
        `Travel info ${travelInfoId} not found`,
      );
    }

    // Update travel info status
    await manager.update(RegistrationTravelInfo, travelInfoId, {
      travelInfoStatus: newStatus as any,
      updatedAt: new Date(),
      ...(userId && { updatedBy: { id: userId } as any }),
    });

    this.logger.log(`[TRAVEL INFO STATUS] Travel info ${travelInfoId} updated to ${newStatus}`);

    // If travel info is completed, check if travel plan is also completed
    if (newStatus === 'completed') {
      const travelPlans = await manager.find(RegistrationTravelPlan, {
        where: { registrationId: travelInfo.registrationId },
      });

      const travelPlanCompleted = travelPlans?.some((tp) => tp.travelPlanStatus === 'completed');
      
      // Only trigger registration status update if both info and plan are completed
      if (travelPlanCompleted) {
        await this.updateRegistrationStatus(
          travelInfo.registrationId,
          'travel',
          manager,
          userId,
        );
      }
    }
  }

  /**
   * Update travel plan status and sync registration status
   * @param travelPlanId Travel plan ID
   * @param newStatus New travel plan status
   * @param manager EntityManager
   * @param userId User making the change
   */
  async updateTravelPlanStatus(
    travelPlanId: number,
    newStatus: string,
    manager: EntityManager,
    userId?: number,
  ): Promise<void> {
    const travelPlan = await manager.findOne(RegistrationTravelPlan, {
      where: { id: travelPlanId },
      relations: ['registration'],
    });

    if (!travelPlan) {
      throw new InifniBadRequestException(
        ERROR_CODES.REGISTRATION_NOT_FOUND,
        null,
        null,
        `Travel plan ${travelPlanId} not found`,
      );
    }

    // Update travel plan status
    await manager.update(RegistrationTravelPlan, travelPlanId, {
      travelPlanStatus: newStatus as any,
      updatedAt: new Date(),
      ...(userId && { updatedBy: { id: userId } as any }),
    });

    this.logger.log(`[TRAVEL PLAN STATUS] Travel plan ${travelPlanId} updated to ${newStatus}`);

    // If travel plan is completed, check if travel info is also completed
    if (newStatus === 'completed') {
      const travelInfos = await manager.find(RegistrationTravelInfo, {
        where: { registrationId: travelPlan.registrationId },
      });

      const travelInfoCompleted = travelInfos?.some((ti) => ti.travelInfoStatus === 'completed');
      
      // Only trigger registration status update if both info and plan are completed
      if (travelInfoCompleted) {
        await this.updateRegistrationStatus(
          travelPlan.registrationId,
          'travel',
          manager,
          userId,
        );
      }
    }
  }

  /**
   * Update approval status and sync registration status
   * @param approvalId Approval ID
   * @param newStatus New approval status
   * @param manager EntityManager
   * @param userId User making the change
   * @param reason Approval/rejection reason
   */
  async updateApprovalStatus(
    approvalId: number,
    newStatus: ApprovalStatusEnum,
    manager: EntityManager,
    userId?: number,
    reason?: string,
  ): Promise<void> {
    const approval = await manager.findOne(RegistrationApproval, {
      where: { id: approvalId },
      relations: ['registration'],
    });

    if (!approval) {
      throw new InifniBadRequestException(
        ERROR_CODES.REGISTRATION_APPROVAL_NOTFOUND,
        null,
        null,
        `Approval ${approvalId} not found`,
      );
    }

    // Update approval status
    await manager.update(RegistrationApproval, approvalId, {
      approvalStatus: newStatus,
      ...(newStatus === ApprovalStatusEnum.APPROVED && { approvalDate: new Date() }),
      ...(newStatus === ApprovalStatusEnum.APPROVED && userId && { approvedBy: userId }),
      ...(newStatus === ApprovalStatusEnum.APPROVED && reason && { approvalReason: reason }),
      ...(newStatus === ApprovalStatusEnum.REJECTED && reason && { rejectionReason: reason }),
      updatedAt: new Date(),
      ...(userId && { updatedBy: { id: userId } as any }),
    });

    this.logger.log(
      `[APPROVAL STATUS] Approval ${approvalId} updated to ${newStatus}${reason ? ` - Reason: ${reason}` : ''}`,
    );

    // Trigger registration status update based on approval outcome
    await this.updateRegistrationStatus(
      approval.registrationId,
      'approval',
      manager,
      userId,
    );
  }
}
