import { WorkflowRequest } from '../models/WorkflowRequest';
import { DealerClaimDetails } from '../models/DealerClaimDetails';
import { DealerProposalDetails } from '../models/DealerProposalDetails';
import { DealerCompletionDetails } from '../models/DealerCompletionDetails';
import { DealerProposalCostItem } from '../models/DealerProposalCostItem';
import { InternalOrder, IOStatus } from '../models/InternalOrder';
import { ClaimBudgetTracking, BudgetStatus } from '../models/ClaimBudgetTracking';
import { ClaimInvoice } from '../models/ClaimInvoice';
import { ClaimCreditNote } from '../models/ClaimCreditNote';
import { DealerCompletionExpense } from '../models/DealerCompletionExpense';
import { ApprovalLevel } from '../models/ApprovalLevel';
import { Participant } from '../models/Participant';
import { User } from '../models/User';
import { DealerClaimHistory, SnapshotType } from '../models/DealerClaimHistory';
import { Document } from '../models/Document';
import { WorkflowService } from './workflow.service';
import { DealerClaimApprovalService } from './dealerClaimApproval.service';
import { generateRequestNumber } from '../utils/helpers';
import { Priority, WorkflowStatus, ApprovalStatus, ParticipantType } from '../types/common.types';
import { sapIntegrationService } from './sapIntegration.service';
import { dmsIntegrationService } from './dmsIntegration.service';
import { notificationService } from './notification.service';
import { activityService } from './activity.service';
import { UserService } from './user.service';
import logger from '../utils/logger';

/**
 * Dealer Claim Service
 * Handles business logic specific to dealer claim management workflow
 */
export class DealerClaimService {
  private workflowService = new WorkflowService();
  private approvalService = new DealerClaimApprovalService();
  private userService = new UserService();

  /**
   * Create a new dealer claim request
   */
  async createClaimRequest(
    userId: string,
    claimData: {
      activityName: string;
      activityType: string;
      dealerCode: string;
      dealerName: string;
      dealerEmail?: string;
      dealerPhone?: string;
      dealerAddress?: string;
      activityDate?: Date;
      location: string;
      requestDescription: string;
      periodStartDate?: Date;
      periodEndDate?: Date;
      estimatedBudget?: number;
      approvers?: Array<{
        email: string;
        name?: string;
        userId?: string;
        level: number;
        tat?: number | string;
        tatType?: 'hours' | 'days';
      }>;
    }
  ): Promise<WorkflowRequest> {
    try {
      // Generate request number
      const requestNumber = await generateRequestNumber();

      // Validate initiator - check if userId is a valid UUID first
      const isValidUUID = (str: string): boolean => {
        const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
        return uuidRegex.test(str);
      };

      if (!isValidUUID(userId)) {
        // If userId is not a UUID (might be Okta ID), try to find by email or other means
        // This shouldn't happen in normal flow, but handle gracefully
        throw new Error(`Invalid initiator ID format. Expected UUID, got: ${userId}`);
      }

      const initiator = await User.findByPk(userId);
      if (!initiator) {
        throw new Error('Initiator not found');
      }

      // Validate approvers array is provided
      if (!claimData.approvers || !Array.isArray(claimData.approvers) || claimData.approvers.length === 0) {
        throw new Error('Approvers array is required. Please assign approvers for all workflow steps.');
      }

      // Now create workflow request (manager is validated)
      // For claim management, requests are submitted immediately (not drafts)
      // Step 1 will be active for dealer to submit proposal
      const now = new Date();
      const workflowRequest = await WorkflowRequest.create({
        initiatorId: userId,
        requestNumber,
        templateType: 'DEALER CLAIM', // Set template type for dealer claim management
        workflowType: 'CLAIM_MANAGEMENT',
        title: `${claimData.activityName} - Claim Request`,
        description: claimData.requestDescription,
        priority: Priority.STANDARD,
        status: WorkflowStatus.PENDING, // Submitted, not draft
        totalLevels: 5, // Fixed 5-step workflow for claim management (Activity Creation, E-Invoice Generation, and Credit Note Confirmation are now activity logs only)
        currentLevel: 1, // Step 1: Dealer Proposal Submission
        totalTatHours: 0, // Will be calculated from approval levels
        isDraft: false, // Not a draft - submitted and ready for workflow
        isDeleted: false,
        submissionDate: now, // Set submission date for SLA tracking (required for overall SLA calculation)
      });

      // Create claim details
      await DealerClaimDetails.create({
        requestId: workflowRequest.requestId,
        activityName: claimData.activityName,
        activityType: claimData.activityType,
        dealerCode: claimData.dealerCode,
        dealerName: claimData.dealerName,
        dealerEmail: claimData.dealerEmail,
        dealerPhone: claimData.dealerPhone,
        dealerAddress: claimData.dealerAddress,
        activityDate: claimData.activityDate,
        location: claimData.location,
        periodStartDate: claimData.periodStartDate,
        periodEndDate: claimData.periodEndDate,
      });

      // Initialize budget tracking with initial estimated budget (if provided)
      await ClaimBudgetTracking.upsert({
        requestId: workflowRequest.requestId,
        initialEstimatedBudget: claimData.estimatedBudget,
        budgetStatus: BudgetStatus.DRAFT,
        currency: 'INR',
      });

      // Create 8 approval levels for claim management workflow from approvers array
      await this.createClaimApprovalLevelsFromApprovers(workflowRequest.requestId, userId, claimData.dealerEmail, claimData.approvers || []);

      // Schedule TAT jobs for Step 1 (Dealer Proposal Submission) - first active step
      // This ensures SLA tracking starts immediately from request creation
      const { tatSchedulerService } = await import('./tatScheduler.service');
      const dealerLevel = await ApprovalLevel.findOne({
        where: {
          requestId: workflowRequest.requestId,
          levelNumber: 1 // Step 1: Dealer Proposal Submission
        }
      });

      if (dealerLevel && dealerLevel.approverId && dealerLevel.levelStartTime) {
        try {
          const workflowPriority = (workflowRequest as any)?.priority || 'STANDARD';
          await tatSchedulerService.scheduleTatJobs(
            workflowRequest.requestId,
            (dealerLevel as any).levelId,
            dealerLevel.approverId,
            Number(dealerLevel.tatHours || 0),
            dealerLevel.levelStartTime,
            workflowPriority
          );
          logger.info(`[DealerClaimService] TAT jobs scheduled for Step 1 (Dealer Proposal Submission) - Priority: ${workflowPriority}`);
        } catch (tatError) {
          logger.error(`[DealerClaimService] Failed to schedule TAT jobs for Step 1:`, tatError);
          // Don't fail request creation if TAT scheduling fails
        }
      }

      // Create participants (initiator, dealer, department lead, finance - exclude system)
      await this.createClaimParticipants(workflowRequest.requestId, userId, claimData.dealerEmail);

      // Get initiator details for activity logging and notifications
      const initiatorName = initiator.displayName || initiator.email || 'User';

      // Log creation activity
      await activityService.log({
        requestId: workflowRequest.requestId,
        type: 'created',
        user: { userId: userId, name: initiatorName },
        timestamp: new Date().toISOString(),
        action: 'Claim request created',
        details: `Claim request "${workflowRequest.title}" created by ${initiatorName} for dealer ${claimData.dealerName}`
      });

      // Send notification to INITIATOR confirming submission
      await notificationService.sendToUsers([userId], {
        title: 'Claim Request Submitted Successfully',
        body: `Your claim request "${workflowRequest.title}" has been submitted successfully.`,
        requestNumber: requestNumber,
        requestId: workflowRequest.requestId,
        url: `/request/${requestNumber}`,
        type: 'request_submitted',
        priority: 'MEDIUM'
      });

      // Get approval levels for notifications
      // Step 1: Dealer Proposal Submission (first active step - log assignment at creation)
      // Subsequent steps will have assignment logged when they become active (via approval service)

      // Notify Step 1 (Dealer) - dealerLevel was already fetched above for TAT scheduling

      if (dealerLevel && dealerLevel.approverId) {
        // Skip notifications for system processes
        const approverEmail = dealerLevel.approverEmail || '';
        const isSystemProcess = approverEmail.toLowerCase() === 'system@royalenfield.com'
          || approverEmail.toLowerCase().includes('system')
          || dealerLevel.approverId === 'system'
          || dealerLevel.approverName === 'System Auto-Process';

        if (!isSystemProcess) {
          // Send notification to Dealer (Step 1) for proposal submission
          await notificationService.sendToUsers([dealerLevel.approverId], {
            title: 'New Claim Request - Proposal Required',
            body: `Claim request "${workflowRequest.title}" requires your proposal submission.`,
            requestNumber: requestNumber,
            requestId: workflowRequest.requestId,
            url: `/request/${requestNumber}`,
            type: 'assignment',
            priority: 'HIGH',
            actionRequired: true
          });

          // Log assignment activity for dealer (Step 1 - first active step)
          await activityService.log({
            requestId: workflowRequest.requestId,
            type: 'assignment',
            user: { userId: userId, name: initiatorName },
            timestamp: new Date().toISOString(),
            action: 'Assigned to dealer',
            details: `Claim request assigned to dealer ${dealerLevel.approverName || dealerLevel.approverEmail || claimData.dealerName} for proposal submission`
          });
        } else {
          logger.info(`[DealerClaimService] Skipping notification for system process: ${approverEmail} at Step 1`);
        }
      }

      // Note: Step 2, 3, and subsequent steps will have assignment activities logged
      // when they become active (when previous step is approved) via the approval service

      logger.info(`[DealerClaimService] Created claim request: ${workflowRequest.requestNumber}`);
      return workflowRequest;
    } catch (error: any) {
      // Log detailed error information for debugging
      const errorDetails: any = {
        message: error.message,
        name: error.name,
      };

      // Sequelize validation errors
      if (error.errors && Array.isArray(error.errors)) {
        errorDetails.validationErrors = error.errors.map((e: any) => ({
          field: e.path,
          message: e.message,
          value: e.value,
        }));
      }

      // Sequelize database errors
      if (error.parent) {
        errorDetails.databaseError = {
          message: error.parent.message,
          code: error.parent.code,
          detail: error.parent.detail,
        };
      }

      logger.error('[DealerClaimService] Error creating claim request:', errorDetails);
      throw error;
    }
  }

  /**
   * Create 5-step approval levels for claim management from approvers array
   * Validates and creates approval levels based on user-provided approvers
   * Note: Activity Creation, E-Invoice Generation, and Credit Note Confirmation are handled as activity logs only, not approval steps
   */
  private async createClaimApprovalLevelsFromApprovers(
    requestId: string,
    initiatorId: string,
    dealerEmail?: string,
    approvers: Array<{
      email: string;
      name?: string;
      userId?: string;
      level: number;
      tat?: number | string;
      tatType?: 'hours' | 'days';
      stepName?: string; // For additional approvers
      isAdditional?: boolean; // Flag for additional approvers
      originalStepLevel?: number; // Original step level for fixed steps
    }> = []
  ): Promise<void> {
    const initiator = await User.findByPk(initiatorId);
    if (!initiator) {
      throw new Error('Initiator not found');
    }

    // Step definitions with default TAT (only manual approval steps)
    // Note: Activity Creation (was level 4), E-Invoice Generation (was level 7), and Credit Note Confirmation (was level 8) 
    // are now handled as activity logs only, not approval steps
    const stepDefinitions = [
      { level: 1, name: 'Dealer Proposal Submission', defaultTat: 72, isAuto: false },
      { level: 2, name: 'Requestor Evaluation', defaultTat: 48, isAuto: false },
      { level: 3, name: 'Department Lead Approval', defaultTat: 72, isAuto: false },
      { level: 4, name: 'Dealer Completion Documents', defaultTat: 120, isAuto: false },
      { level: 5, name: 'Requestor Claim Approval', defaultTat: 48, isAuto: false },
    ];

    // Sort approvers by level to process in order
    const sortedApprovers = [...approvers].sort((a, b) => a.level - b.level);

    // Track which original steps have been processed
    const processedOriginalSteps = new Set<number>();

    // Process approvers in order by their level
    for (const approver of sortedApprovers) {
      let approverId: string | null = null;
      let approverEmail = '';
      let approverName = 'System';
      let tatHours = 48; // Default TAT
      let levelName = '';
      let isSystemStep = false;
      let isFinalApprover = false;

      // Find the step definition this approver belongs to
      let stepDef = null;

      // Check if this is a system step by email (for backwards compatibility)
      const isSystemEmail = approver.email === 'system@royalenfield.com' || approver.email === 'finance@royalenfield.com';

      if (approver.isAdditional) {
        // Additional approver - use stepName from frontend
        levelName = approver.stepName || 'Additional Approver';
        isSystemStep = false;
        isFinalApprover = false;
      } else {
        // Fixed step - find by originalStepLevel first, then by matching level
        const originalLevel = approver.originalStepLevel || approver.level;
        stepDef = stepDefinitions.find(s => s.level === originalLevel);

        if (!stepDef) {
          // Try to find by current level if originalStepLevel not provided
          stepDef = stepDefinitions.find(s => s.level === approver.level);
        }

        // System steps (Activity Creation, E-Invoice Generation, Credit Note Confirmation) are no longer approval steps
        // They are handled as activity logs only
        // If approver has system email but no step definition found, skip creating approval level
        if (!stepDef && isSystemEmail) {
          logger.info(`[DealerClaimService] Skipping system step approver at level ${approver.level} - system steps are now activity logs only`);
          continue; // Skip creating approval level for system steps
        }

        if (stepDef) {
          levelName = stepDef.name;
          isSystemStep = false; // No system steps in approval levels anymore
          isFinalApprover = stepDef.level === 5; // Last step is now Requestor Claim Approval (level 5)
          processedOriginalSteps.add(stepDef.level);
        } else {
          // Fallback - shouldn't happen but handle gracefully
          levelName = `Step ${approver.level}`;
          isSystemStep = false;
          logger.warn(`[DealerClaimService] Could not find step definition for approver at level ${approver.level}, using fallback name`);
        }

        // Ensure levelName is never empty and truncate if too long (max 100 chars)
        if (!levelName || levelName.trim() === '') {
          levelName = approver.isAdditional
            ? `Additional Approver - Level ${approver.level}`
            : `Step ${approver.level}`;
          logger.warn(`[DealerClaimService] levelName was empty for approver at level ${approver.level}, using fallback: ${levelName}`);
        }

        // Truncate levelName to max 100 characters (database constraint)
        if (levelName.length > 100) {
          logger.warn(`[DealerClaimService] levelName too long (${levelName.length} chars) for level ${approver.level}, truncating to 100 chars`);
          levelName = levelName.substring(0, 97) + '...';
        }
      }

      // System steps are no longer created as approval levels - they are activity logs only
      // This code path should not be reached anymore, but kept for safety
      if (isSystemStep) {
        logger.warn(`[DealerClaimService] System step detected but should not create approval level. Skipping.`);
        continue; // Skip creating approval level for system steps
      }

      {
        // User-provided approver (fixed or additional)
        if (!approver.email) {
          throw new Error(`Approver email is required for level ${approver.level}: ${levelName}`);
        }

        // Calculate TAT in hours
        if (approver.tat) {
          const tat = Number(approver.tat);
          if (isNaN(tat) || tat <= 0) {
            throw new Error(`Invalid TAT for level ${approver.level}. TAT must be a positive number.`);
          }
          tatHours = approver.tatType === 'days' ? tat * 24 : tat;
        } else if (stepDef) {
          tatHours = stepDef.defaultTat;
        }

        // Ensure user exists in database (create from Okta if needed)
        let user: User | null = null;

        // Helper function to check if a string is a valid UUID
        const isValidUUID = (str: string): boolean => {
          const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
          return uuidRegex.test(str);
        };

        // Try to find user by userId if it's a valid UUID
        if (approver.userId && isValidUUID(approver.userId)) {
          try {
            user = await User.findByPk(approver.userId);
          } catch (error: any) {
            // If findByPk fails (e.g., invalid UUID format), log and continue to email lookup
            logger.debug(`[DealerClaimService] Could not find user by userId ${approver.userId}, will try email lookup`);
          }
        }

        // If user not found by ID (or userId was not a valid UUID), try email
        if (!user && approver.email) {
          user = await User.findOne({ where: { email: approver.email.toLowerCase() } });

          if (!user) {
            // User doesn't exist - create from Okta
            logger.info(`[DealerClaimService] User ${approver.email} not found in DB, syncing from Okta`);
            try {
              user = await this.userService.ensureUserExists({
                email: approver.email.toLowerCase(),
                userId: approver.userId, // Pass Okta ID if provided (ensureUserExists will handle it)
              }) as any;
              logger.info(`[DealerClaimService] Successfully synced user ${approver.email} from Okta`);
            } catch (oktaError: any) {
              logger.error(`[DealerClaimService] Failed to sync user from Okta: ${approver.email}`, oktaError);
              throw new Error(`User email '${approver.email}' not found in organization directory. Please verify the email address.`);
            }
          }
        }

        if (!user) {
          throw new Error(`Could not resolve user for level ${approver.level}: ${approver.email}`);
        }

        approverId = user.userId;
        approverEmail = user.email;
        approverName = approver.name || user.displayName || user.email || 'Approver';
      }

      // Ensure we have a valid approverId
      if (!approverId) {
        logger.error(`[DealerClaimService] No approverId resolved for level ${approver.level}, using initiator as fallback`);
        approverId = initiatorId;
        approverEmail = approverEmail || initiator.email;
        approverName = approverName || 'Unknown Approver';
      }

      // Ensure approverId is a valid UUID before creating
      const isValidUUID = (str: string): boolean => {
        const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
        return uuidRegex.test(str);
      };

      if (!approverId || !isValidUUID(approverId)) {
        logger.error(`[DealerClaimService] Invalid approverId for level ${approver.level}: ${approverId}`);
        throw new Error(`Invalid approver ID format for level ${approver.level}. Expected UUID.`);
      }

      // Create approval level using the approver's level (which may be shifted)
      const now = new Date();
      const isStep1 = approver.level === 1;

      try {
        // Check for duplicate level_number for this request_id (unique constraint)
        const existingLevel = await ApprovalLevel.findOne({
          where: {
            requestId,
            levelNumber: approver.level
          }
        });

        if (existingLevel) {
          logger.error(`[DealerClaimService] Duplicate level number ${approver.level} already exists for request ${requestId}`);
          throw new Error(`Level ${approver.level} already exists for this request. This may indicate a duplicate approver.`);
        }

        await ApprovalLevel.create({
          requestId,
          levelNumber: approver.level, // Use the approver's level (may be shifted)
          levelName: levelName, // Already validated and truncated above
          approverId: approverId,
          approverEmail: approverEmail || '',
          approverName: approverName || 'Unknown',
          tatHours: tatHours || 0,
          status: isStep1 ? ApprovalStatus.PENDING : ApprovalStatus.PENDING,
          isFinalApprover: isFinalApprover || false,
          elapsedHours: 0,
          remainingHours: tatHours || 0,
          tatPercentageUsed: 0,
          levelStartTime: isStep1 ? now : undefined,
          tatStartTime: isStep1 ? now : undefined,
          // Note: tatDays is NOT included - it's auto-calculated by the database
        } as any);
      } catch (createError: any) {
        // Log detailed validation errors
        const errorDetails: any = {
          message: createError.message,
          name: createError.name,
          level: approver.level,
          levelName: levelName?.substring(0, 50), // Truncate for logging
          approverId,
          approverEmail,
          approverName: approverName?.substring(0, 50),
          tatHours,
        };

        // Sequelize validation errors
        if (createError.errors && Array.isArray(createError.errors)) {
          errorDetails.validationErrors = createError.errors.map((e: any) => ({
            field: e.path,
            message: e.message,
            value: e.value,
            type: e.type,
          }));
        }

        // Database constraint errors
        if (createError.parent) {
          errorDetails.databaseError = {
            message: createError.parent.message,
            code: createError.parent.code,
            detail: createError.parent.detail,
            constraint: createError.parent.constraint,
          };
        }

        logger.error(`[DealerClaimService] Failed to create approval level for level ${approver.level}:`, errorDetails);
        throw new Error(`Failed to create approval level ${approver.level} (${levelName}): ${createError.message}`);
      }
    }

    // Validate that required fixed steps were processed
    const requiredSteps = stepDefinitions.filter(s => !s.isAuto);
    for (const requiredStep of requiredSteps) {
      if (!processedOriginalSteps.has(requiredStep.level)) {
        logger.warn(`[DealerClaimService] Required step ${requiredStep.level} (${requiredStep.name}) was not found in approvers array`);
      }
    }
  }

  /**
   * Create participants for claim management workflow
   * Includes: Initiator, Dealer, Department Lead, Finance Approver
   * Excludes: System users
   */
  private async createClaimParticipants(
    requestId: string,
    initiatorId: string,
    dealerEmail?: string
  ): Promise<void> {
    try {
      const initiator = await User.findByPk(initiatorId);
      if (!initiator) {
        throw new Error('Initiator not found');
      }

      // Get all approval levels to extract approvers
      const approvalLevels = await ApprovalLevel.findAll({
        where: { requestId },
        order: [['levelNumber', 'ASC']],
      });

      const participantsToAdd: Array<{
        userId: string;
        userEmail: string;
        userName: string;
        participantType: ParticipantType;
      }> = [];

      // 1. Add Initiator
      participantsToAdd.push({
        userId: initiatorId,
        userEmail: initiator.email,
        userName: initiator.displayName || initiator.email || 'Initiator',
        participantType: ParticipantType.INITIATOR,
      });

      // 2. Add Dealer (treated as Okta/internal user - sync from Okta if needed)
      if (dealerEmail && dealerEmail.toLowerCase() !== 'system@royalenfield.com') {
        let dealerUser = await User.findOne({
          where: { email: dealerEmail.toLowerCase() },
        });

        if (!dealerUser) {
          logger.info(`[DealerClaimService] Dealer ${dealerEmail} not found in DB for participants, syncing from Okta`);
          try {
            dealerUser = await this.userService.ensureUserExists({
              email: dealerEmail.toLowerCase(),
            }) as any;
            logger.info(`[DealerClaimService] Successfully synced dealer ${dealerEmail} from Okta for participants`);
          } catch (oktaError: any) {
            logger.error(`[DealerClaimService] Failed to sync dealer from Okta for participants: ${dealerEmail}`, oktaError);
            // Don't throw - dealer might be added later, but log the error
            logger.warn(`[DealerClaimService] Skipping dealer participant creation for ${dealerEmail}`);
          }
        }

        if (dealerUser) {
          participantsToAdd.push({
            userId: dealerUser.userId,
            userEmail: dealerUser.email,
            userName: dealerUser.displayName || dealerUser.email || 'Dealer',
            participantType: ParticipantType.APPROVER,
          });
        }
      }

      // 3. Add all approvers from approval levels (excluding system and duplicates)
      const addedUserIds = new Set<string>([initiatorId]);
      const systemEmails = ['system@royalenfield.com'];

      for (const level of approvalLevels) {
        const approverEmail = (level as any).approverEmail?.toLowerCase();
        const approverId = (level as any).approverId;

        // Skip if system user or already added
        if (
          !approverId ||
          systemEmails.includes(approverEmail || '') ||
          addedUserIds.has(approverId)
        ) {
          continue;
        }

        // Skip if email is system email
        if (approverEmail && systemEmails.includes(approverEmail)) {
          continue;
        }

        // Helper function to check if a string is a valid UUID
        const isValidUUID = (str: string): boolean => {
          const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
          return uuidRegex.test(str);
        };

        // Only try to find user if approverId is a valid UUID
        if (!isValidUUID(approverId)) {
          logger.warn(`[DealerClaimService] Invalid UUID format for approverId: ${approverId}, skipping participant creation`);
          continue;
        }

        const approverUser = await User.findByPk(approverId);
        if (approverUser) {
          participantsToAdd.push({
            userId: approverId,
            userEmail: approverUser.email,
            userName: approverUser.displayName || approverUser.email || 'Approver',
            participantType: ParticipantType.APPROVER,
          });
          addedUserIds.add(approverId);
        }
      }

      // Create participants (deduplicate by userId)
      const participantMap = new Map<string, typeof participantsToAdd[0]>();
      const rolePriority: Record<string, number> = {
        'INITIATOR': 3,
        'APPROVER': 2,
        'SPECTATOR': 1,
      };

      for (const participantData of participantsToAdd) {
        const existing = participantMap.get(participantData.userId);
        if (existing) {
          // Keep higher priority role
          const existingPriority = rolePriority[existing.participantType] || 0;
          const newPriority = rolePriority[participantData.participantType] || 0;
          if (newPriority > existingPriority) {
            participantMap.set(participantData.userId, participantData);
          }
        } else {
          participantMap.set(participantData.userId, participantData);
        }
      }

      // Create participant records
      for (const participantData of participantMap.values()) {
        await Participant.create({
          requestId,
          userId: participantData.userId,
          userEmail: participantData.userEmail,
          userName: participantData.userName,
          participantType: participantData.participantType,
          canComment: true,
          canViewDocuments: true,
          canDownloadDocuments: true,
          notificationEnabled: true,
          addedBy: initiatorId,
          isActive: true,
        } as any);
      }

      logger.info(`[DealerClaimService] Created ${participantMap.size} participants for claim request ${requestId}`);
    } catch (error) {
      logger.error('[DealerClaimService] Error creating participants:', error);
      // Don't throw - participants are not critical for request creation
    }
  }

  /**
   * Resolve Department Lead based on initiator's department/manager
   * If multiple users found with same department, uses the first one
   */
  /**
   * Resolve Department Lead/Manager by searching Okta using manager's displayName
   * Flow:
   * 1. Get manager displayName from initiator's user record
   * 2. Search Okta directory by displayName
   * 3. If empty: Return null (no manager found, fallback to old method)
   * 4. If single: Use that user, create in DB if doesn't exist, return user
   * 5. If multiple: Throw error with list of users (frontend will show confirmation)
   * 
   * @param initiator - The user creating the claim request
   * @returns User object for department lead/manager, or null if not found
   * @throws Error if multiple managers found (frontend should handle confirmation)
   */
  private async resolveDepartmentLeadFromManager(initiator: User): Promise<User | null> {
    try {
      // Get manager displayName from initiator's user record
      const managerDisplayName = initiator.manager; // This is the displayName of the manager

      if (!managerDisplayName) {
        logger.warn(`[DealerClaimService] Initiator ${initiator.email} has no manager displayName set`);
        // Return null - caller will handle the error
        return null;
      }

      logger.info(`[DealerClaimService] Searching Okta for manager with displayName: "${managerDisplayName}"`);

      // Search Okta by displayName
      const oktaUsers = await this.userService.searchOktaByDisplayName(managerDisplayName);

      if (oktaUsers.length === 0) {
        logger.warn(`[DealerClaimService] No reporting manager found in Okta for displayName: "${managerDisplayName}"`);
        // Return null - caller will handle the error
        return null;
      }

      if (oktaUsers.length === 1) {
        // Single match - use this user
        const oktaUser = oktaUsers[0];
        const managerEmail = oktaUser.profile.email || oktaUser.profile.login;

        logger.info(`[DealerClaimService] Found single manager match: ${managerEmail} for displayName: "${managerDisplayName}"`);

        // Check if user exists in DB, create if doesn't exist
        const managerUser = await this.userService.ensureUserExists({
          userId: oktaUser.id,
          email: managerEmail,
          displayName: oktaUser.profile.displayName || `${oktaUser.profile.firstName || ''} ${oktaUser.profile.lastName || ''}`.trim(),
          firstName: oktaUser.profile.firstName,
          lastName: oktaUser.profile.lastName,
          department: oktaUser.profile.department,
          phone: oktaUser.profile.mobilePhone,
        });

        return managerUser;
      }

      // Multiple matches - throw error with list for frontend confirmation
      const managerOptions = oktaUsers.map(u => ({
        userId: u.id,
        email: u.profile.email || u.profile.login,
        displayName: u.profile.displayName || `${u.profile.firstName || ''} ${u.profile.lastName || ''}`.trim(),
        firstName: u.profile.firstName,
        lastName: u.profile.lastName,
        department: u.profile.department,
      }));

      logger.warn(`[DealerClaimService] Multiple managers found (${oktaUsers.length}) for displayName: "${managerDisplayName}"`);

      // Create a custom error with the manager options
      const error: any = new Error(`Multiple reporting managers found. Please select one.`);
      error.code = 'MULTIPLE_MANAGERS_FOUND';
      error.managers = managerOptions;
      throw error;

    } catch (error: any) {
      // If it's our custom multiple managers error, re-throw it
      if (error.code === 'MULTIPLE_MANAGERS_FOUND') {
        throw error;
      }

      // For other errors, log and fallback to old method
      logger.error(`[DealerClaimService] Error resolving manager from Okta:`, error);
      return await this.resolveDepartmentLead(initiator);
    }
  }

  /**
   * Legacy method: Resolve Department Lead using old logic
   * Kept as fallback when Okta search fails or manager displayName not set
   */
  private async resolveDepartmentLead(initiator: User): Promise<User | null> {
    try {
      const { Op } = await import('sequelize');

      logger.info(`[DealerClaimService] Resolving department lead for initiator: ${initiator.email}, department: ${initiator.department}, manager: ${initiator.manager}`);

      // Priority 1: Find user with MANAGEMENT role in same department
      if (initiator.department) {
        const deptLeads = await User.findAll({
          where: {
            department: initiator.department,
            role: 'MANAGEMENT' as any,
            isActive: true,
          },
          order: [['createdAt', 'ASC']], // Get first one if multiple
          limit: 1,
        });
        if (deptLeads.length > 0) {
          logger.info(`[DealerClaimService] Found department lead by MANAGEMENT role: ${deptLeads[0].email} for department: ${initiator.department}`);
          return deptLeads[0];
        } else {
          logger.debug(`[DealerClaimService] No MANAGEMENT role user found in department: ${initiator.department}`);
        }
      } else {
        logger.debug(`[DealerClaimService] Initiator has no department set`);
      }

      // Priority 2: Find users with "Department Lead", "Team Lead", "Team Manager", "Group Manager", "Assistant Manager", "Deputy Manager" in designation, same department
      if (initiator.department) {
        const leads = await User.findAll({
          where: {
            department: initiator.department,
            designation: {
              [Op.or]: [
                { [Op.iLike]: '%department lead%' },
                { [Op.iLike]: '%departmentlead%' },
                { [Op.iLike]: '%dept lead%' },
                { [Op.iLike]: '%deptlead%' },
                { [Op.iLike]: '%team lead%' },
                { [Op.iLike]: '%team manager%' },
                { [Op.iLike]: '%group manager%' },
                { [Op.iLike]: '%assistant manager%' },
                { [Op.iLike]: '%deputy manager%' },
                { [Op.iLike]: '%lead%' },
                { [Op.iLike]: '%head%' },
                { [Op.iLike]: '%manager%' },
              ],
            } as any,
            isActive: true,
          },
          order: [['createdAt', 'ASC']], // Get first one if multiple
          limit: 1,
        });
        if (leads.length > 0) {
          logger.info(`[DealerClaimService] Found lead by designation: ${leads[0].email} (designation: ${leads[0].designation})`);
          return leads[0];
        }
      }

      // Priority 3: Use initiator's manager field
      if (initiator.manager) {
        const manager = await User.findOne({
          where: {
            email: initiator.manager,
            isActive: true,
          },
        });
        if (manager) {
          logger.info(`[DealerClaimService] Using initiator's manager as department lead: ${manager.email}`);
          return manager;
        }
      }

      // Priority 4: Find any user in same department (fallback - use first one)
      if (initiator.department) {
        const anyDeptUser = await User.findOne({
          where: {
            department: initiator.department,
            isActive: true,
            userId: { [Op.ne]: initiator.userId }, // Exclude initiator
          },
          order: [['createdAt', 'ASC']],
        });
        if (anyDeptUser) {
          logger.warn(`[DealerClaimService] Using first available user in department as fallback: ${anyDeptUser.email} (designation: ${anyDeptUser.designation}, role: ${anyDeptUser.role})`);
          return anyDeptUser;
        } else {
          logger.debug(`[DealerClaimService] No other users found in department: ${initiator.department}`);
        }
      }

      // Priority 5: Search across all departments for users with "Department Lead" designation
      logger.debug(`[DealerClaimService] Trying to find any user with "Department Lead" designation...`);
      const anyDeptLead = await User.findOne({
        where: {
          designation: {
            [Op.iLike]: '%department lead%',
          } as any,
          isActive: true,
          userId: { [Op.ne]: initiator.userId }, // Exclude initiator
        },
        order: [['createdAt', 'ASC']],
      });
      if (anyDeptLead) {
        logger.warn(`[DealerClaimService] Found user with "Department Lead" designation across all departments: ${anyDeptLead.email} (department: ${anyDeptLead.department})`);
        return anyDeptLead;
      }

      // Priority 6: Find any user with MANAGEMENT role (across all departments)
      logger.debug(`[DealerClaimService] Trying to find any user with MANAGEMENT role...`);
      const anyManagementUser = await User.findOne({
        where: {
          role: 'MANAGEMENT' as any,
          isActive: true,
          userId: { [Op.ne]: initiator.userId }, // Exclude initiator
        },
        order: [['createdAt', 'ASC']],
      });
      if (anyManagementUser) {
        logger.warn(`[DealerClaimService] Found user with MANAGEMENT role across all departments: ${anyManagementUser.email} (department: ${anyManagementUser.department})`);
        return anyManagementUser;
      }

      // Priority 7: Find any user with ADMIN role (across all departments)
      logger.debug(`[DealerClaimService] Trying to find any user with ADMIN role...`);
      const anyAdminUser = await User.findOne({
        where: {
          role: 'ADMIN' as any,
          isActive: true,
          userId: { [Op.ne]: initiator.userId }, // Exclude initiator
        },
        order: [['createdAt', 'ASC']],
      });
      if (anyAdminUser) {
        logger.warn(`[DealerClaimService] Found user with ADMIN role as fallback: ${anyAdminUser.email} (department: ${anyAdminUser.department})`);
        return anyAdminUser;
      }

      logger.warn(`[DealerClaimService] Could not resolve department lead for initiator: ${initiator.email} (department: ${initiator.department || 'NOT SET'}, manager: ${initiator.manager || 'NOT SET'})`);
      logger.warn(`[DealerClaimService] No suitable department lead found. Please ensure:`);
      logger.warn(`[DealerClaimService] 1. Initiator has a department set: ${initiator.department || 'MISSING'}`);
      logger.warn(`[DealerClaimService] 2. There is at least one user with MANAGEMENT role in the system`);
      logger.warn(`[DealerClaimService] 3. Initiator's manager field is set: ${initiator.manager || 'MISSING'}`);
      return null;
    } catch (error) {
      logger.error('[DealerClaimService] Error resolving department lead:', error);
      return null;
    }
  }

  /**
   * Resolve Finance Team approver for Step 8
   */
  private async resolveFinanceApprover(): Promise<User | null> {
    try {
      const { Op } = await import('sequelize');

      // Priority 1: Find user with department containing "Finance" and MANAGEMENT role
      const financeManager = await User.findOne({
        where: {
          department: {
            [Op.iLike]: '%finance%',
          } as any,
          role: 'MANAGEMENT' as any,
        },
        order: [['createdAt', 'DESC']],
      });
      if (financeManager) {
        logger.info(`[DealerClaimService] Found finance manager: ${financeManager.email}`);
        return financeManager;
      }

      // Priority 2: Find user with designation containing "Finance" or "Accountant"
      const financeUser = await User.findOne({
        where: {
          [Op.or]: [
            { designation: { [Op.iLike]: '%finance%' } as any },
            { designation: { [Op.iLike]: '%accountant%' } as any },
          ],
        },
        order: [['createdAt', 'DESC']],
      });
      if (financeUser) {
        logger.info(`[DealerClaimService] Found finance user by designation: ${financeUser.email}`);
        return financeUser;
      }

      // Priority 3: Check admin configurations for finance team email
      const { getConfigValue } = await import('./configReader.service');
      const financeEmail = await getConfigValue('FINANCE_TEAM_EMAIL');
      if (financeEmail) {
        const financeUserByEmail = await User.findOne({
          where: { email: financeEmail },
        });
        if (financeUserByEmail) {
          logger.info(`[DealerClaimService] Found finance user from config: ${financeEmail}`);
          return financeUserByEmail;
        }
      }

      logger.warn('[DealerClaimService] Could not resolve finance approver, will use default email');
      return null;
    } catch (error) {
      logger.error('[DealerClaimService] Error resolving finance approver:', error);
      return null;
    }
  }

  /**
   * Get claim details with all related data
   */
  async getClaimDetails(requestId: string): Promise<any> {
    try {
      const request = await WorkflowRequest.findByPk(requestId, {
        include: [
          { model: User, as: 'initiator' },
          { model: ApprovalLevel, as: 'approvalLevels' },
        ]
      });

      if (!request) {
        throw new Error('Request not found');
      }

      // Handle backward compatibility: workflowType may be undefined in old environments
      const workflowType = request.workflowType || 'NON_TEMPLATIZED';
      if (workflowType !== 'CLAIM_MANAGEMENT') {
        throw new Error('Request is not a claim management request');
      }

      // Fetch related claim data separately
      const claimDetails = await DealerClaimDetails.findOne({
        where: { requestId }
      });

      const proposalDetails = await DealerProposalDetails.findOne({
        where: { requestId },
        include: [
          {
            model: DealerProposalCostItem,
            as: 'costItems',
            required: false,
            separate: true, // Use separate query for ordering
            order: [['itemOrder', 'ASC']]
          }
        ]
      });

      const completionDetails = await DealerCompletionDetails.findOne({
        where: { requestId }
      });

      // Fetch Internal Order details
      const internalOrder = await InternalOrder.findOne({
        where: { requestId },
        include: [
          { model: User, as: 'organizer', required: false }
        ]
      });

      // Serialize claim details to ensure proper field names
      let serializedClaimDetails = null;
      if (claimDetails) {
        serializedClaimDetails = (claimDetails as any).toJSON ? (claimDetails as any).toJSON() : claimDetails;
      }

      // Transform proposal details to include cost items as array
      let transformedProposalDetails = null;
      if (proposalDetails) {
        const proposalData = (proposalDetails as any).toJSON ? (proposalDetails as any).toJSON() : proposalDetails;

        // Get cost items from separate table (dealer_proposal_cost_items)
        let costBreakup: any[] = [];
        if (proposalData.costItems && Array.isArray(proposalData.costItems) && proposalData.costItems.length > 0) {
          // Use cost items from separate table
          costBreakup = proposalData.costItems.map((item: any) => ({
            description: item.itemDescription || item.description,
            amount: Number(item.amount) || 0
          }));
        }
        // Note: costBreakup JSONB field has been removed - only using separate table now

        transformedProposalDetails = {
          ...proposalData,
          costBreakup, // Always return as array for frontend compatibility
          costItems: proposalData.costItems || [] // Also include raw cost items
        };
      }

      // Serialize completion details
      let serializedCompletionDetails = null;
      if (completionDetails) {
        serializedCompletionDetails = (completionDetails as any).toJSON ? (completionDetails as any).toJSON() : completionDetails;
      }

      // Serialize internal order details
      let serializedInternalOrder = null;
      if (internalOrder) {
        serializedInternalOrder = (internalOrder as any).toJSON ? (internalOrder as any).toJSON() : internalOrder;
      }

      // Fetch Budget Tracking details
      const budgetTracking = await ClaimBudgetTracking.findOne({
        where: { requestId }
      });

      // Fetch Invoice details
      const claimInvoice = await ClaimInvoice.findOne({
        where: { requestId }
      });

      // Fetch Credit Note details
      const claimCreditNote = await ClaimCreditNote.findOne({
        where: { requestId }
      });

      // Fetch Completion Expenses (individual expense items)
      const completionExpenses = await DealerCompletionExpense.findAll({
        where: { requestId },
        order: [['createdAt', 'ASC']]
      });

      // Serialize new tables
      let serializedBudgetTracking = null;
      if (budgetTracking) {
        serializedBudgetTracking = (budgetTracking as any).toJSON ? (budgetTracking as any).toJSON() : budgetTracking;
      }

      let serializedInvoice = null;
      if (claimInvoice) {
        serializedInvoice = (claimInvoice as any).toJSON ? (claimInvoice as any).toJSON() : claimInvoice;
      }

      let serializedCreditNote = null;
      if (claimCreditNote) {
        serializedCreditNote = (claimCreditNote as any).toJSON ? (claimCreditNote as any).toJSON() : claimCreditNote;
      }

      // Transform completion expenses to array format for frontend
      const expensesBreakdown = completionExpenses.map((expense: any) => {
        const expenseData = expense.toJSON ? expense.toJSON() : expense;
        return {
          description: expenseData.description || '',
          amount: Number(expenseData.amount) || 0
        };
      });

      return {
        request: (request as any).toJSON ? (request as any).toJSON() : request,
        claimDetails: serializedClaimDetails,
        proposalDetails: transformedProposalDetails,
        completionDetails: serializedCompletionDetails,
        internalOrder: serializedInternalOrder,
        // New normalized tables
        budgetTracking: serializedBudgetTracking,
        invoice: serializedInvoice,
        creditNote: serializedCreditNote,
        completionExpenses: expensesBreakdown, // Array of expense items
      };
    } catch (error) {
      logger.error('[DealerClaimService] Error getting claim details:', error);
      throw error;
    }
  }

  /**
   * Submit dealer proposal (Step 1)
   */
  async submitDealerProposal(
    requestId: string,
    proposalData: {
      proposalDocumentPath?: string;
      proposalDocumentUrl?: string;
      costBreakup: any[];
      totalEstimatedBudget: number;
      timelineMode: 'date' | 'days';
      expectedCompletionDate?: Date;
      expectedCompletionDays?: number;
      dealerComments: string;
    },
    dealerUserId?: string // Optional dealer user ID for history tracking
  ): Promise<void> {
    try {
      const request = await WorkflowRequest.findByPk(requestId);
      if (!request || request.workflowType !== 'CLAIM_MANAGEMENT') {
        throw new Error('Invalid claim request');
      }

      // Get dealer user ID if not provided - try to find by dealer email from claim details
      let actualDealerUserId: string | null = dealerUserId || null;
      if (!actualDealerUserId) {
        const claimDetails = await DealerClaimDetails.findOne({ where: { requestId } });
        if (claimDetails?.dealerEmail) {
          const dealerUser = await User.findOne({
            where: { email: claimDetails.dealerEmail }
          });
          actualDealerUserId = dealerUser?.userId || null;
        }
      }

      if (request.currentLevel !== 1) {
        throw new Error('Proposal can only be submitted at step 1');
      }

      // Save proposal details (costBreakup removed - now using separate table)
      const [proposal] = await DealerProposalDetails.upsert({
        requestId,
        proposalDocumentPath: proposalData.proposalDocumentPath,
        proposalDocumentUrl: proposalData.proposalDocumentUrl,
        // costBreakup field removed - now using dealer_proposal_cost_items table
        totalEstimatedBudget: proposalData.totalEstimatedBudget,
        timelineMode: proposalData.timelineMode,
        expectedCompletionDate: proposalData.expectedCompletionDate,
        expectedCompletionDays: proposalData.expectedCompletionDays,
        dealerComments: proposalData.dealerComments,
        submittedAt: new Date(),
      }, {
        returning: true
      });

      // Get proposalId - handle both Sequelize instance and plain object
      let proposalId = (proposal as any).proposalId
        || (proposal as any).proposal_id;

      // If not found, try getDataValue method
      if (!proposalId && (proposal as any).getDataValue) {
        proposalId = (proposal as any).getDataValue('proposalId');
      }

      // If still not found, fetch the proposal by requestId
      if (!proposalId) {
        const existingProposal = await DealerProposalDetails.findOne({
          where: { requestId }
        });
        if (existingProposal) {
          proposalId = (existingProposal as any).proposalId
            || (existingProposal as any).proposal_id
            || ((existingProposal as any).getDataValue ? (existingProposal as any).getDataValue('proposalId') : null);
        }
      }

      if (!proposalId) {
        throw new Error('Failed to get proposal ID after saving proposal details');
      }

      // Save cost items to separate table (preferred approach)
      if (proposalData.costBreakup && proposalData.costBreakup.length > 0) {
        // Delete existing cost items for this proposal (in case of update)
        await DealerProposalCostItem.destroy({
          where: { proposalId }
        });

        // Insert new cost items
        const costItems = proposalData.costBreakup.map((item: any, index: number) => ({
          proposalId,
          requestId,
          itemDescription: item.description || item.itemDescription || '',
          amount: Number(item.amount) || 0,
          itemOrder: index
        }));

        await DealerProposalCostItem.bulkCreate(costItems);
        logger.info(`[DealerClaimService] Saved ${costItems.length} cost items for proposal ${proposalId}`);
      }

      // Update budget tracking with proposal estimate
      await ClaimBudgetTracking.upsert({
        requestId,
        proposalEstimatedBudget: proposalData.totalEstimatedBudget,
        proposalSubmittedAt: new Date(),
        budgetStatus: BudgetStatus.PROPOSED,
        currency: 'INR',
      });

      // Approve Dealer Proposal Submission step dynamically (by levelName, not hardcoded step number)
      let dealerProposalLevel = await ApprovalLevel.findOne({
        where: {
          requestId,
          levelName: 'Dealer Proposal Submission'
        }
      });

      // Fallback: try to find by levelNumber 1 (for backwards compatibility)
      if (!dealerProposalLevel) {
        dealerProposalLevel = await ApprovalLevel.findOne({
          where: { requestId, levelNumber: 1 }
        });
      }

      if (dealerProposalLevel) {
        // Use dealer's comment if provided, otherwise use default message
        const approvalComment = proposalData.dealerComments?.trim()
          ? proposalData.dealerComments.trim()
          : 'Dealer proposal submitted';

        // Perform the approval action FIRST - only save snapshot if action succeeds
        await this.approvalService.approveLevel(
          dealerProposalLevel.levelId,
          { action: 'APPROVE', comments: approvalComment },
          actualDealerUserId || (request as any).initiatorId || 'system', // Use dealer or initiator ID
          { ipAddress: null, userAgent: null }
        );

        // Save proposal history AFTER approval succeeds (this is the only snapshot needed for dealer submission)
        // Use dealer user ID if available, otherwise use initiator ID as fallback
        const historyUserId = actualDealerUserId || (request as any).initiatorId || null;
        if (!historyUserId) {
          logger.warn(`[DealerClaimService] No user ID available for proposal history, skipping history save`);
        } else {
          try {
            await this.saveProposalHistory(
              requestId,
              dealerProposalLevel.levelId,
              dealerProposalLevel.levelNumber,
              `Proposal Submitted: ${approvalComment}`,
              historyUserId
            );
            // Note: We don't save workflow history here - proposal history is sufficient
            // Workflow history will be saved when the level is approved and moves to next level
          } catch (snapshotError) {
            // Log error but don't fail the submission - snapshot is for audit, not critical
            logger.error(`[DealerClaimService] Failed to save proposal history snapshot (non-critical):`, snapshotError);
          }
        }
      }

      logger.info(`[DealerClaimService] Dealer proposal submitted for request: ${requestId}`);
    } catch (error) {
      logger.error('[DealerClaimService] Error submitting dealer proposal:', error);
      throw error;
    }
  }

  /**
   * Submit dealer completion documents (Step 5)
   */
  async submitCompletionDocuments(
    requestId: string,
    completionData: {
      activityCompletionDate: Date;
      numberOfParticipants?: number;
      closedExpenses: any[];
      totalClosedExpenses: number;
      invoicesReceipts?: any[];
      attendanceSheet?: any;
      completionDescription?: string;
    },
    dealerUserId?: string // Optional dealer user ID for history tracking
  ): Promise<void> {
    try {
      const request = await WorkflowRequest.findByPk(requestId);
      // Handle backward compatibility: workflowType may be undefined in old environments
      const workflowType = request?.workflowType || 'NON_TEMPLATIZED';
      if (!request || workflowType !== 'CLAIM_MANAGEMENT') {
        throw new Error('Invalid claim request');
      }

      // Find the "Dealer Completion Documents" step by levelName (handles step shifts due to additional approvers)
      const approvalLevels = await ApprovalLevel.findAll({
        where: { requestId },
        order: [['levelNumber', 'ASC']]
      });

      const dealerCompletionStep = approvalLevels.find((level: any) => {
        const levelName = (level.levelName || '').toLowerCase();
        return levelName.includes('dealer completion') || levelName.includes('completion documents');
      });

      if (!dealerCompletionStep) {
        throw new Error('Dealer Completion Documents step not found');
      }

      // Check if current level matches the Dealer Completion Documents step (handles step shifts)
      if (request.currentLevel !== dealerCompletionStep.levelNumber) {
        throw new Error(`Completion documents can only be submitted at the Dealer Completion Documents step (currently at step ${request.currentLevel})`);
      }

      // Save completion details
      const [completionDetails] = await DealerCompletionDetails.upsert({
        requestId,
        activityCompletionDate: completionData.activityCompletionDate,
        numberOfParticipants: completionData.numberOfParticipants,
        totalClosedExpenses: completionData.totalClosedExpenses,
        submittedAt: new Date(),
      });

      // Persist individual closed expenses to dealer_completion_expenses
      const completionId = (completionDetails as any)?.completionId;
      if (completionData.closedExpenses && completionData.closedExpenses.length > 0) {
        // Clear existing expenses for this request to avoid duplicates
        await DealerCompletionExpense.destroy({ where: { requestId } });
        const expenseRows = completionData.closedExpenses.map((item: any) => ({
          requestId,
          completionId,
          description: item.description,
          amount: item.amount,
        }));
        await DealerCompletionExpense.bulkCreate(expenseRows);
      }

      // Update budget tracking with closed expenses
      await ClaimBudgetTracking.upsert({
        requestId,
        closedExpenses: completionData.totalClosedExpenses,
        closedExpensesSubmittedAt: new Date(),
        budgetStatus: BudgetStatus.CLOSED,
        currency: 'INR',
      });

      // Approve Dealer Completion Documents step dynamically (by levelName, not hardcoded step number)
      let dealerCompletionLevel = await ApprovalLevel.findOne({
        where: {
          requestId,
          levelName: 'Dealer Completion Documents'
        }
      });

      // Fallback: try to find by levelNumber 4 (new position after removing system steps)
      if (!dealerCompletionLevel) {
        dealerCompletionLevel = await ApprovalLevel.findOne({
          where: { requestId, levelNumber: 4 }
        });
      }

      if (dealerCompletionLevel) {
        // Use dealer's completion description if provided, otherwise use default message
        const approvalComment = completionData.completionDescription?.trim()
          ? completionData.completionDescription.trim()
          : 'Completion documents submitted';

        // Get dealer user ID if not provided - try to find by dealer email from claim details
        let actualDealerUserId: string | null = dealerUserId || null;
        if (!actualDealerUserId) {
          const claimDetails = await DealerClaimDetails.findOne({ where: { requestId } });
          if (claimDetails?.dealerEmail) {
            const dealerUser = await User.findOne({
              where: { email: claimDetails.dealerEmail }
            });
            actualDealerUserId = dealerUser?.userId || null;
          }
        }

        // Perform the approval action FIRST - only save snapshot if action succeeds
        await this.approvalService.approveLevel(
          dealerCompletionLevel.levelId,
          { action: 'APPROVE', comments: approvalComment },
          actualDealerUserId || (request as any).initiatorId || 'system',
          { ipAddress: null, userAgent: null }
        );

        // Save completion history AFTER approval succeeds (this is the only snapshot needed for dealer completion)
        // Use dealer user ID if available, otherwise use initiator ID as fallback
        const historyUserId = actualDealerUserId || (request as any).initiatorId || null;
        if (!historyUserId) {
          logger.warn(`[DealerClaimService] No user ID available for completion history, skipping history save`);
        } else {
          try {
            await this.saveCompletionHistory(
              requestId,
              dealerCompletionLevel.levelId,
              dealerCompletionLevel.levelNumber,
              `Completion Submitted: ${approvalComment}`,
              historyUserId
            );
            // Note: We don't save workflow history here - completion history is sufficient
            // Workflow history will be saved when the level is approved and moves to next level
          } catch (snapshotError) {
            // Log error but don't fail the submission - snapshot is for audit, not critical
            logger.error(`[DealerClaimService] Failed to save completion history snapshot (non-critical):`, snapshotError);
          }
        }
      }

      logger.info(`[DealerClaimService] Completion documents submitted for request: ${requestId}`);
    } catch (error) {
      logger.error('[DealerClaimService] Error submitting completion documents:', error);
      throw error;
    }
  }

  /**
   * Update IO details (Step 3 - Department Lead)
   * Validates IO number with SAP and blocks budget
   */
  /**
   * Update IO details and block amount in SAP
   * Only stores data when blocking amount > 0
   * This method is called when user actually blocks the amount
   */
  async updateIODetails(
    requestId: string,
    ioData: {
      ioNumber: string;
      ioRemark?: string;
      availableBalance?: number;
      blockedAmount?: number;
      remainingBalance?: number;
    },
    organizedByUserId?: string
  ): Promise<void> {
    try {
      // Ensure blockedAmount is rounded to exactly 2 decimal places from the start
      const blockedAmount = ioData.blockedAmount ? parseFloat(ioData.blockedAmount.toFixed(2)) : 0;

      // If blocking amount > 0, proceed with SAP integration and blocking
      // If blocking amount is 0 but ioNumber is provided, just save the IO details without blocking
      if (blockedAmount <= 0) {
        // Allow saving IO details (ioNumber only) even without blocking amount
        // This is useful when Requestor Evaluation is in progress but amount hasn't been blocked yet
        if (ioData.ioNumber) {
          const organizedBy = organizedByUserId || null;

          // Create or update Internal Order record with just IO details (no blocking)
          const [internalOrder, created] = await InternalOrder.findOrCreate({
            where: { requestId },
            defaults: {
              requestId,
              ioNumber: ioData.ioNumber,
              ioRemark: ioData.ioRemark || '', // Optional - kept for backward compatibility // Optional - keep for backward compatibility
              ioAvailableBalance: ioData.availableBalance || 0,
              ioBlockedAmount: 0,
              ioRemainingBalance: ioData.remainingBalance || 0,
              organizedBy: organizedBy || undefined,
              organizedAt: new Date(),
              status: IOStatus.PENDING,
            }
          });

          if (!created) {
            // Update existing IO record with new IO details
            // IMPORTANT: When updating existing record, preserve balance fields from previous blocking
            // Only update ioNumber - don't overwrite balance values
            await internalOrder.update({
              ioNumber: ioData.ioNumber,
              // Don't update balance fields for existing records - preserve values from previous blocking
              // Only update organizedBy and organizedAt
              organizedBy: organizedBy || internalOrder.organizedBy,
              organizedAt: new Date(),
            });

            logger.info(`[DealerClaimService] IO details updated (preserved existing balance values) for request: ${requestId}`, {
              ioNumber: ioData.ioNumber,
              preservedAvailableBalance: internalOrder.ioAvailableBalance,
              preservedBlockedAmount: internalOrder.ioBlockedAmount,
              preservedRemainingBalance: internalOrder.ioRemainingBalance,
            });
          }

          logger.info(`[DealerClaimService] IO details saved (without blocking) for request: ${requestId}`, {
            ioNumber: ioData.ioNumber
          });

          return; // Exit early - no SAP blocking needed
        } else {
          throw new Error('Blocked amount must be greater than 0, or ioNumber must be provided');
        }
      }

      // Validate IO number with SAP
      const ioValidation = await sapIntegrationService.validateIONumber(ioData.ioNumber);

      if (!ioValidation.isValid) {
        throw new Error(`Invalid IO number: ${ioValidation.error || 'IO number not found in SAP'}`);
      }

      // Block budget in SAP
      const request = await WorkflowRequest.findByPk(requestId);
      const requestNumber = request ? ((request as any).requestNumber || (request as any).request_number) : 'UNKNOWN';

      logger.info(`[DealerClaimService] Blocking budget in SAP:`, {
        requestId,
        requestNumber,
        ioNumber: ioData.ioNumber,
        amountToBlock: blockedAmount,
        availableBalance: ioData.availableBalance || ioValidation.availableBalance,
      });

      const blockResult = await sapIntegrationService.blockBudget(
        ioData.ioNumber,
        blockedAmount,
        requestNumber,
        `Budget block for claim request ${requestNumber}`
      );

      if (!blockResult.success) {
        throw new Error(`Failed to block budget in SAP: ${blockResult.error}`);
      }

      const sapReturnedBlockedAmount = blockResult.blockedAmount;
      // Extract SAP reference number from blockId (this is the Sap_Reference_no from SAP response)
      // Only use the actual SAP reference number - don't use any generated fallback
      const sapDocumentNumber = blockResult.blockId || undefined;
      // Ensure availableBalance is rounded to 2 decimal places for accurate calculations
      const availableBalance = parseFloat((ioData.availableBalance || ioValidation.availableBalance).toFixed(2));

      // Log if SAP reference number was received
      if (sapDocumentNumber) {
        logger.info(`[DealerClaimService] ✅ SAP Reference Number received: ${sapDocumentNumber}`);
      } else {
        logger.warn(`[DealerClaimService] ⚠️ No SAP Reference Number received from SAP response`);
      }

      // Use the amount we REQUESTED for calculation, not what SAP returned
      // SAP might return a slightly different amount due to rounding, but we calculate based on what we requested
      // Only use SAP's returned amount if it's significantly different (more than 1 rupee), which would indicate an actual issue
      const amountDifference = Math.abs(sapReturnedBlockedAmount - blockedAmount);
      const useSapAmount = amountDifference > 1.0; // Only use SAP's amount if difference is more than 1 rupee
      const finalBlockedAmount = useSapAmount ? sapReturnedBlockedAmount : blockedAmount;

      // Log SAP response vs what we sent
      logger.info(`[DealerClaimService] SAP block result:`, {
        requestedAmount: blockedAmount,
        sapReturnedBlockedAmount: sapReturnedBlockedAmount,
        sapReturnedRemainingBalance: blockResult.remainingBalance,
        sapDocumentNumber: sapDocumentNumber, // SAP reference number from response
        availableBalance,
        amountDifference,
        usingSapAmount: useSapAmount,
        finalBlockedAmountUsed: finalBlockedAmount,
      });

      // Warn if SAP blocked a significantly different amount than requested
      if (amountDifference > 0.01) {
        if (amountDifference > 1.0) {
          logger.warn(`[DealerClaimService] ⚠️ Significant amount mismatch! Requested: ${blockedAmount}, SAP blocked: ${sapReturnedBlockedAmount}, Difference: ${amountDifference}`);
        } else {
          logger.info(`[DealerClaimService] Minor amount difference (likely rounding): Requested: ${blockedAmount}, SAP returned: ${sapReturnedBlockedAmount}, Using requested amount for calculation`);
        }
      }

      // Calculate remaining balance: availableBalance - requestedAmount
      // IMPORTANT: Use the amount we REQUESTED, not SAP's returned amount (unless SAP blocked significantly different amount)
      // This ensures accuracy: remaining = available - requested
      // Round to 2 decimal places to avoid floating point precision issues
      const calculatedRemainingBalance = parseFloat((availableBalance - finalBlockedAmount).toFixed(2));

      // Only use SAP's value if it's valid AND matches our calculation (within 1 rupee tolerance)
      // This is a safety check - if SAP's value is way off, use our calculation
      // Round SAP's value to 2 decimal places for consistency
      const sapRemainingBalance = blockResult.remainingBalance ? parseFloat(blockResult.remainingBalance.toFixed(2)) : 0;
      const sapValueIsValid = sapRemainingBalance > 0 &&
        sapRemainingBalance <= availableBalance &&
        Math.abs(sapRemainingBalance - calculatedRemainingBalance) < 1;

      const remainingBalance = sapValueIsValid
        ? sapRemainingBalance
        : calculatedRemainingBalance;

      // Ensure remaining balance is not negative and round to 2 decimal places
      const finalRemainingBalance = parseFloat(Math.max(0, remainingBalance).toFixed(2));

      // Warn if SAP's value doesn't match our calculation
      if (!sapValueIsValid && sapRemainingBalance !== calculatedRemainingBalance) {
        logger.warn(`[DealerClaimService] ⚠️ SAP returned invalid remaining balance (${sapRemainingBalance}), using calculated value (${calculatedRemainingBalance})`);
      }

      logger.info(`[DealerClaimService] Budget blocking calculation:`, {
        availableBalance,
        blockedAmount: finalBlockedAmount,
        sapRemainingBalance,
        calculatedRemainingBalance,
        finalRemainingBalance
      });

      // Get the user who is blocking the IO (current user)
      const organizedBy = organizedByUserId || null;

      // Round amounts to exactly 2 decimal places for database storage (avoid floating point precision issues)
      // Use parseFloat with toFixed to ensure exact 2 decimal precision
      const roundedAvailableBalance = parseFloat(availableBalance.toFixed(2));
      const roundedBlockedAmount = parseFloat(finalBlockedAmount.toFixed(2));
      const roundedRemainingBalance = parseFloat(finalRemainingBalance.toFixed(2));

      // Create or update Internal Order record (only when blocking)
      const ioRecordData = {
        requestId,
        ioNumber: ioData.ioNumber,
        ioRemark: ioData.ioRemark || '', // Optional - kept for backward compatibility
        ioAvailableBalance: roundedAvailableBalance,
        ioBlockedAmount: roundedBlockedAmount,
        ioRemainingBalance: roundedRemainingBalance,
        sapDocumentNumber: sapDocumentNumber, // Store SAP reference number
        organizedBy: organizedBy || undefined,
        organizedAt: new Date(),
        status: IOStatus.BLOCKED,
      };

      logger.info(`[DealerClaimService] Storing IO details in database:`, {
        ioNumber: ioData.ioNumber,
        ioAvailableBalance: availableBalance,
        ioBlockedAmount: finalBlockedAmount,
        ioRemainingBalance: finalRemainingBalance,
        sapDocumentNumber: sapDocumentNumber,
        requestId
      });

      const [internalOrder, created] = await InternalOrder.findOrCreate({
        where: { requestId },
        defaults: ioRecordData
      });

      if (!created) {
        // Update existing IO record - explicitly update all fields including remainingBalance
        logger.info(`[DealerClaimService] Updating existing IO record for request: ${requestId}`);
        logger.info(`[DealerClaimService] Update data:`, {
          ioRemainingBalance: ioRecordData.ioRemainingBalance,
          ioBlockedAmount: ioRecordData.ioBlockedAmount,
          ioAvailableBalance: ioRecordData.ioAvailableBalance,
          sapDocumentNumber: ioRecordData.sapDocumentNumber
        });

        // Explicitly update all fields to ensure remainingBalance is saved
        const updateResult = await internalOrder.update({
          ioNumber: ioRecordData.ioNumber,
          ioRemark: ioRecordData.ioRemark,
          ioAvailableBalance: ioRecordData.ioAvailableBalance,
          ioBlockedAmount: ioRecordData.ioBlockedAmount,
          ioRemainingBalance: ioRecordData.ioRemainingBalance, // Explicitly ensure this is updated
          sapDocumentNumber: ioRecordData.sapDocumentNumber, // Update SAP document number
          organizedBy: ioRecordData.organizedBy,
          organizedAt: ioRecordData.organizedAt,
          status: ioRecordData.status
        });

        logger.info(`[DealerClaimService] Update result:`, updateResult ? 'Success' : 'Failed');
      } else {
        logger.info(`[DealerClaimService] Created new IO record for request: ${requestId}`);
      }

      // Verify what was actually saved - reload from database
      await internalOrder.reload();
      const savedRemainingBalance = internalOrder.ioRemainingBalance;

      logger.info(`[DealerClaimService] ✅ IO record after save (verified from database):`, {
        ioId: internalOrder.ioId,
        ioNumber: internalOrder.ioNumber,
        ioAvailableBalance: internalOrder.ioAvailableBalance,
        ioBlockedAmount: internalOrder.ioBlockedAmount,
        ioRemainingBalance: savedRemainingBalance,
        expectedRemainingBalance: finalRemainingBalance,
        match: savedRemainingBalance === finalRemainingBalance || Math.abs((savedRemainingBalance || 0) - finalRemainingBalance) < 0.01,
        status: internalOrder.status
      });

      // Warn if remaining balance doesn't match
      if (Math.abs((savedRemainingBalance || 0) - finalRemainingBalance) >= 0.01) {
        logger.error(`[DealerClaimService] ⚠️ WARNING: Remaining balance mismatch! Expected: ${finalRemainingBalance}, Saved: ${savedRemainingBalance}`);
      }

      // Save IO history after successful blocking
      // Find the Department Lead IO Approval level (Step 3)
      const ioApprovalLevel = await ApprovalLevel.findOne({
        where: {
          requestId,
          levelName: 'Department Lead IO Approval'
        }
      });

      // Fallback: try to find by levelNumber 3
      const ioLevel = ioApprovalLevel || await ApprovalLevel.findOne({
        where: { requestId, levelNumber: 3 }
      });

      // Get user ID for history - use organizedBy if it's a UUID, otherwise try to find user
      let ioHistoryUserId: string | null = null;
      if (ioLevel) {
        if (organizedBy) {
          // Check if organizedBy is a valid UUID
          const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
          if (uuidRegex.test(organizedBy)) {
            ioHistoryUserId = organizedBy;
          } else {
            // Try to find user by email or name
            const user = await User.findOne({
              where: { email: organizedBy }
            });
            ioHistoryUserId = user?.userId || null;
          }
        }

        // Fallback to initiator if no user found
        if (!ioHistoryUserId) {
          const request = await WorkflowRequest.findByPk(requestId);
          ioHistoryUserId = (request as any)?.initiatorId || null;
        }
      }

      // Update budget tracking with blocked amount FIRST
      await ClaimBudgetTracking.upsert({
        requestId,
        ioBlockedAmount: finalBlockedAmount,
        ioBlockedAt: new Date(),
        budgetStatus: BudgetStatus.BLOCKED,
        currency: 'INR',
      });

      // Save IO history AFTER budget tracking update succeeds (only if ioLevel exists)
      if (ioLevel && ioHistoryUserId) {
        try {
          await this.saveIOHistory(
            requestId,
            ioLevel.levelId,
            ioLevel.levelNumber,
            `IO Blocked: ₹${finalBlockedAmount.toFixed(2)} blocked in SAP`,
            ioHistoryUserId
          );
        } catch (snapshotError) {
          // Log error but don't fail the IO blocking - snapshot is for audit, not critical
          logger.error(`[DealerClaimService] Failed to save IO history snapshot (non-critical):`, snapshotError);
        }
      } else if (ioLevel && !ioHistoryUserId) {
        logger.warn(`[DealerClaimService] No user ID available for IO history, skipping history save`);
      }

      logger.info(`[DealerClaimService] IO blocked for request: ${requestId}`, {
        ioNumber: ioData.ioNumber,
        blockedAmount: finalBlockedAmount,
        availableBalance,
        remainingBalance: finalRemainingBalance
      });
    } catch (error) {
      logger.error('[DealerClaimService] Error blocking IO:', error);
      throw error;
    }
  }

  /**
   * Update e-invoice details (Step 7)
   * Generates e-invoice via DMS integration
   */
  async updateEInvoiceDetails(
    requestId: string,
    invoiceData?: {
      eInvoiceNumber?: string;
      eInvoiceDate?: Date;
      dmsNumber?: string;
      amount?: number;
      description?: string;
    }
  ): Promise<void> {
    try {
      const claimDetails = await DealerClaimDetails.findOne({ where: { requestId } });
      if (!claimDetails) {
        throw new Error('Claim details not found');
      }

      const budgetTracking = await ClaimBudgetTracking.findOne({ where: { requestId } });
      const completionDetails = await DealerCompletionDetails.findOne({ where: { requestId } });
      const internalOrder = await InternalOrder.findOne({ where: { requestId } });
      const claimInvoice = await ClaimInvoice.findOne({ where: { requestId } });

      const request = await WorkflowRequest.findByPk(requestId);
      if (!request) {
        throw new Error('Workflow request not found');
      }

      const workflowType = (request as any).workflowType;
      if (workflowType !== 'CLAIM_MANAGEMENT') {
        throw new Error('This endpoint is only for claim management workflows');
      }

      const requestNumber = request ? ((request as any).requestNumber || (request as any).request_number) : 'UNKNOWN';

      // If invoice data not provided, generate via DMS
      if (!invoiceData?.eInvoiceNumber) {
        const proposalDetails = await DealerProposalDetails.findOne({ where: { requestId } });
        const invoiceAmount = invoiceData?.amount
          || proposalDetails?.totalEstimatedBudget
          || budgetTracking?.proposalEstimatedBudget
          || budgetTracking?.initialEstimatedBudget
          || 0;

        const invoiceResult = await dmsIntegrationService.generateEInvoice({
          requestNumber,
          dealerCode: claimDetails.dealerCode,
          dealerName: claimDetails.dealerName,
          amount: invoiceAmount,
          description: invoiceData?.description || `E-Invoice for claim request ${requestNumber}`,
          ioNumber: internalOrder?.ioNumber || undefined,
        });

        if (!invoiceResult.success) {
          throw new Error(`Failed to generate e-invoice: ${invoiceResult.error}`);
        }

        await ClaimInvoice.upsert({
          requestId,
          invoiceNumber: invoiceResult.eInvoiceNumber,
          invoiceDate: invoiceResult.invoiceDate || new Date(),
          dmsNumber: invoiceResult.dmsNumber,
          amount: invoiceAmount,
          status: 'GENERATED',
          generatedAt: new Date(),
          description: invoiceData?.description || `E-Invoice for claim request ${requestNumber}`,
        });

        logger.info(`[DealerClaimService] E-Invoice generated via DMS for request: ${requestId}`, {
          eInvoiceNumber: invoiceResult.eInvoiceNumber,
          dmsNumber: invoiceResult.dmsNumber
        });
      } else {
        // Manual entry - just update the fields
        await ClaimInvoice.upsert({
          requestId,
          invoiceNumber: invoiceData.eInvoiceNumber,
          invoiceDate: invoiceData.eInvoiceDate || new Date(),
          dmsNumber: invoiceData.dmsNumber,
          amount: invoiceData.amount,
          status: 'UPDATED',
          generatedAt: new Date(),
          description: invoiceData.description,
        });

        logger.info(`[DealerClaimService] E-Invoice details manually updated for request: ${requestId}`);
      }

      // Check if Requestor Claim Approval is approved - if not, approve it first
      // Find dynamically by levelName (handles step shifts due to additional approvers)
      const approvalLevels = await ApprovalLevel.findAll({
        where: { requestId },
        order: [['levelNumber', 'ASC']]
      });

      let requestorClaimLevel = approvalLevels.find((level: any) => {
        const levelName = (level.levelName || '').toLowerCase();
        return levelName.includes('requestor') &&
          (levelName.includes('claim') || levelName.includes('approval'));
      });

      // Fallback: try to find by levelNumber 5 (new position after removing system steps)
      // But only if no match found by name (handles edge cases)
      if (!requestorClaimLevel) {
        requestorClaimLevel = approvalLevels.find((level: any) => level.levelNumber === 5);
      }

      // Validate that we're at the Requestor Claim Approval step before allowing DMS push
      if (requestorClaimLevel && request.currentLevel !== requestorClaimLevel.levelNumber) {
        throw new Error(`Cannot push to DMS. Request is currently at step ${request.currentLevel}, but Requestor Claim Approval is at step ${requestorClaimLevel.levelNumber}. Please complete all previous steps first.`);
      }

      if (requestorClaimLevel && requestorClaimLevel.status !== ApprovalStatus.APPROVED) {
        logger.info(`[DealerClaimService] Requestor Claim Approval not approved yet. Auto-approving for request ${requestId}`);
        // Auto-approve Requestor Claim Approval
        await this.approvalService.approveLevel(
          requestorClaimLevel.levelId,
          { action: 'APPROVE', comments: 'Auto-approved when pushing to DMS. E-Invoice generation will be logged as activity.' },
          'system',
          { ipAddress: null, userAgent: 'System Auto-Process' }
        );
        logger.info(`[DealerClaimService] Requestor Claim Approval approved. E-Invoice generation will be logged as activity when DMS webhook is received.`);
      } else {
        // Requestor Claim Approval already approved
        logger.info(`[DealerClaimService] Requestor Claim Approval already approved. E-Invoice generation will be logged as activity when DMS webhook is received.`);
      }

      // Log E-Invoice generation as activity (no approval level needed)
      await activityService.log({
        requestId,
        type: 'status_change',
        user: { userId: 'system', name: 'System Auto-Process' },
        timestamp: new Date().toISOString(),
        action: 'E-Invoice Generation Initiated',
        details: `E-Invoice generation initiated via DMS integration for request ${requestNumber}. Waiting for DMS webhook confirmation.`,
      });
    } catch (error) {
      logger.error('[DealerClaimService] Error updating e-invoice details:', error);
      throw error;
    }
  }

  /**
   * Log E-Invoice Generation as activity (no longer an approval step)
   * This method logs the e-invoice generation activity when invoice is generated via DMS webhook
   */
  async logEInvoiceGenerationActivity(requestId: string, invoiceNumber?: string): Promise<void> {
    try {
      logger.info(`[DealerClaimService] Logging E-Invoice Generation activity for request ${requestId}`);

      const request = await WorkflowRequest.findByPk(requestId);
      if (!request) {
        throw new Error(`Workflow request ${requestId} not found`);
      }

      const workflowType = (request as any).workflowType;
      if (workflowType !== 'CLAIM_MANAGEMENT') {
        logger.warn(`[DealerClaimService] Skipping E-Invoice activity logging - not a claim management workflow (type: ${workflowType})`);
        return;
      }

      const requestNumber = (request as any).requestNumber || (request as any).request_number || 'UNKNOWN';
      const claimInvoice = await ClaimInvoice.findOne({ where: { requestId } });
      const finalInvoiceNumber = invoiceNumber || claimInvoice?.invoiceNumber || 'N/A';

      // Log E-Invoice Generation as activity
      await activityService.log({
        requestId,
        type: 'status_change',
        user: { userId: 'system', name: 'System Auto-Process' },
        timestamp: new Date().toISOString(),
        action: 'E-Invoice Generated',
        details: `E-Invoice generated via DMS. Invoice Number: ${finalInvoiceNumber}. Request: ${requestNumber}`,
      });

      logger.info(`[DealerClaimService] E-Invoice Generation activity logged for request ${requestId} (Invoice: ${finalInvoiceNumber})`);
    } catch (error) {
      logger.error(`[DealerClaimService] Error logging E-Invoice Generation activity for request ${requestId}:`, error);
      // Don't throw - activity logging is not critical
    }
  }

  /**
   * Update credit note details (Step 8)
   * Generates credit note via DMS integration
   */
  async updateCreditNoteDetails(
    requestId: string,
    creditNoteData?: {
      creditNoteNumber?: string;
      creditNoteDate?: Date;
      creditNoteAmount?: number;
      reason?: string;
      description?: string;
    }
  ): Promise<void> {
    try {
      const claimDetails = await DealerClaimDetails.findOne({ where: { requestId } });
      if (!claimDetails) {
        throw new Error('Claim details not found');
      }

      const budgetTracking = await ClaimBudgetTracking.findOne({ where: { requestId } });
      const completionDetails = await DealerCompletionDetails.findOne({ where: { requestId } });
      const claimInvoice = await ClaimInvoice.findOne({ where: { requestId } });

      const request = await WorkflowRequest.findByPk(requestId);
      const requestNumber = request ? ((request as any).requestNumber || (request as any).request_number) : 'UNKNOWN';

      // If credit note data not provided, generate via DMS
      if (!creditNoteData?.creditNoteNumber) {
        const creditNoteAmount = creditNoteData?.creditNoteAmount
          || budgetTracking?.closedExpenses
          || completionDetails?.totalClosedExpenses
          || 0;

        // Only generate via DMS if invoice exists, otherwise allow manual entry
        if (claimInvoice?.invoiceNumber) {
          const creditNoteResult = await dmsIntegrationService.generateCreditNote({
            requestNumber,
            eInvoiceNumber: claimInvoice.invoiceNumber,
            dealerCode: claimDetails.dealerCode,
            dealerName: claimDetails.dealerName,
            amount: creditNoteAmount,
            reason: creditNoteData?.reason || 'Claim settlement',
            description: creditNoteData?.description || `Credit note for claim request ${requestNumber}`,
          });

          if (!creditNoteResult.success) {
            throw new Error(`Failed to generate credit note: ${creditNoteResult.error}`);
          }

          await ClaimCreditNote.upsert({
            requestId,
            invoiceId: claimInvoice.invoiceId,
            creditNoteNumber: creditNoteResult.creditNoteNumber,
            creditNoteDate: creditNoteResult.creditNoteDate || new Date(),
            creditNoteAmount: creditNoteResult.creditNoteAmount,
            status: 'GENERATED',
            confirmedAt: new Date(),
            reason: creditNoteData?.reason || 'Claim settlement',
            description: creditNoteData?.description || `Credit note for claim request ${requestNumber}`,
          });

          logger.info(`[DealerClaimService] Credit note generated via DMS for request: ${requestId}`, {
            creditNoteNumber: creditNoteResult.creditNoteNumber,
            creditNoteAmount: creditNoteResult.creditNoteAmount
          });
        } else {
          // No invoice exists - create credit note manually without invoice link
          await ClaimCreditNote.upsert({
            requestId,
            invoiceId: undefined, // No invoice linked
            creditNoteNumber: undefined, // Will be set manually later
            creditNoteDate: creditNoteData?.creditNoteDate || new Date(),
            creditNoteAmount: creditNoteAmount,
            status: 'PENDING',
            reason: creditNoteData?.reason || 'Claim settlement',
            description: creditNoteData?.description || `Credit note for claim request ${requestNumber} (no invoice)`,
          });

          logger.info(`[DealerClaimService] Credit note created without invoice for request: ${requestId}`);
        }
      } else {
        // Manual entry - just update the fields
        await ClaimCreditNote.upsert({
          requestId,
          invoiceId: claimInvoice?.invoiceId || undefined, // Allow undefined if no invoice
          creditNoteNumber: creditNoteData.creditNoteNumber,
          creditNoteDate: creditNoteData.creditNoteDate || new Date(),
          creditNoteAmount: creditNoteData.creditNoteAmount,
          status: 'UPDATED',
          confirmedAt: new Date(),
          reason: creditNoteData?.reason,
          description: creditNoteData?.description,
        });

        logger.info(`[DealerClaimService] Credit note details manually updated for request: ${requestId}`);
      }
    } catch (error) {
      logger.error('[DealerClaimService] Error updating credit note details:', error);
      throw error;
    }
  }

  /**
   * Send credit note to dealer and auto-approve Step 8
   * This method sends the credit note to the dealer via email/notification and auto-approves Step 8
   */
  async sendCreditNoteToDealer(requestId: string, userId: string): Promise<void> {
    try {
      logger.info(`[DealerClaimService] Sending credit note to dealer for request ${requestId}`);

      // Get credit note details
      const creditNote = await ClaimCreditNote.findOne({
        where: { requestId }
      });

      if (!creditNote) {
        throw new Error('Credit note not found. Please ensure credit note is generated before sending to dealer.');
      }

      // Get claim details for dealer information
      const claimDetails = await DealerClaimDetails.findOne({ where: { requestId } });
      if (!claimDetails) {
        throw new Error('Claim details not found');
      }

      // Get workflow request
      const request = await WorkflowRequest.findByPk(requestId);
      if (!request) {
        throw new Error('Workflow request not found');
      }

      const workflowType = (request as any).workflowType;
      if (workflowType !== 'CLAIM_MANAGEMENT') {
        throw new Error('This operation is only available for claim management workflows');
      }

      // Credit Note Confirmation is now an activity log only, not an approval step
      const requestNumber = (request as any).requestNumber || (request as any).request_number || 'UNKNOWN';

      // Update credit note status to CONFIRMED
      await creditNote.update({
        status: 'CONFIRMED',
        confirmedAt: new Date(),
        confirmedBy: userId,
      });

      // Log Credit Note Confirmation as activity (no approval step needed)
      await activityService.log({
        requestId,
        type: 'status_change',
        user: { userId: userId, name: 'Finance Team' },
        timestamp: new Date().toISOString(),
        action: 'Credit Note Confirmed and Sent',
        details: `Credit note sent to dealer. Credit Note Number: ${creditNote.creditNoteNumber || 'N/A'}. Credit Note Amount: ₹${creditNote.creditNoteAmount || 0}. Request: ${requestNumber}`,
      });

      // Send notification to dealer (you can implement email service here)
      logger.info(`[DealerClaimService] Credit note sent to dealer`, {
        requestId,
        creditNoteNumber: creditNote.creditNoteNumber,
        dealerEmail: claimDetails.dealerEmail,
        dealerName: claimDetails.dealerName,
      });

      // TODO: Implement email service to send credit note to dealer
      // await emailService.sendCreditNoteToDealer({
      //   dealerEmail: claimDetails.dealerEmail,
      //   dealerName: claimDetails.dealerName,
      //   creditNoteNumber: creditNote.creditNoteNumber,
      //   creditNoteAmount: creditNote.creditNoteAmount,
      //   requestNumber: requestNumber,
      // });

    } catch (error) {
      logger.error('[DealerClaimService] Error sending credit note to dealer:', error);
      throw error;
    }
  }

  /**
   * Process Activity Creation (now activity log only, not an approval step)
   * Creates activity confirmation and sends emails to dealer, requestor, and department lead
   * Logs activity instead of creating/approving approval level
   */
  async processActivityCreation(requestId: string): Promise<void> {
    try {
      logger.info(`[DealerClaimService] Processing Activity Creation for request ${requestId}`);

      // Get workflow request
      const request = await WorkflowRequest.findByPk(requestId);
      if (!request) {
        throw new Error(`Workflow request ${requestId} not found`);
      }

      // Verify this is a claim management workflow
      const workflowType = (request as any).workflowType;
      if (workflowType !== 'CLAIM_MANAGEMENT') {
        logger.warn(`[DealerClaimService] Skipping Activity Creation - not a claim management workflow (type: ${workflowType})`);
        return;
      }

      // Get claim details
      const claimDetails = await DealerClaimDetails.findOne({ where: { requestId } });
      if (!claimDetails) {
        throw new Error(`Claim details not found for request ${requestId}`);
      }

      // Get participants for email notifications
      const initiator = await User.findByPk((request as any).initiatorId);
      const dealerUser = claimDetails.dealerEmail
        ? await User.findOne({ where: { email: claimDetails.dealerEmail } })
        : null;

      // Get department lead dynamically (by levelName, not hardcoded step number)
      let deptLeadLevel = await ApprovalLevel.findOne({
        where: {
          requestId,
          levelName: 'Department Lead Approval'
        }
      });

      // Fallback: try to find by levelNumber 3 (for backwards compatibility)
      if (!deptLeadLevel) {
        deptLeadLevel = await ApprovalLevel.findOne({
          where: {
            requestId,
            levelNumber: 3
          }
        });
      }
      const departmentLead = deptLeadLevel?.approverId
        ? await User.findByPk(deptLeadLevel.approverId)
        : null;

      const requestNumber = (request as any).requestNumber || (request as any).request_number || 'UNKNOWN';
      const activityName = claimDetails.activityName || 'Activity';
      const activityType = claimDetails.activityType || 'N/A';

      // Prepare email recipients
      const emailRecipients: string[] = [];
      const userIdsForNotification: string[] = [];

      // Add initiator
      if (initiator) {
        emailRecipients.push(initiator.email);
        userIdsForNotification.push(initiator.userId);
      }

      // Add dealer
      if (dealerUser) {
        emailRecipients.push(dealerUser.email);
        userIdsForNotification.push(dealerUser.userId);
      } else if (claimDetails.dealerEmail) {
        emailRecipients.push(claimDetails.dealerEmail);
      }

      // Add department lead
      if (departmentLead) {
        emailRecipients.push(departmentLead.email);
        userIdsForNotification.push(departmentLead.userId);
      }

      // Send activity confirmation emails
      const emailSubject = `Activity Created: ${activityName} - ${requestNumber}`;
      const emailBody = `Activity "${activityName}" (${activityType}) has been created successfully for request ${requestNumber}. IO confirmation to be made.`;

      // Send notifications to users in the system with proper metadata
      if (userIdsForNotification.length > 0) {
        // Prepare metadata for activity created email template
        const activityData = {
          activityName: activityName,
          activityType: activityType,
          activityDate: claimDetails.activityDate,
          location: claimDetails.location || 'Not specified',
          dealerName: claimDetails.dealerName || 'Dealer',
          dealerCode: claimDetails.dealerCode,
          initiatorName: initiator ? (initiator.displayName || initiator.email) : 'Initiator',
          departmentLeadName: departmentLead ? (departmentLead.displayName || departmentLead.email) : undefined,
          ioNumber: undefined, // IO number will be added later when IO is created
          nextSteps: 'IO confirmation to be made. Dealer will proceed with activity execution and submit completion documents.'
        };

        await notificationService.sendToUsers(userIdsForNotification, {
          title: emailSubject,
          body: emailBody,
          requestId,
          requestNumber,
          url: `/request/${requestNumber}`,
          type: 'activity_created',
          priority: 'MEDIUM',
          actionRequired: false,
          metadata: {
            activityData: activityData
          }
        });
      }

      // Log Activity Creation as activity (no approval level needed)
      await activityService.log({
        requestId,
        type: 'status_change',
        user: { userId: 'system', name: 'System Auto-Process' },
        timestamp: new Date().toISOString(),
        action: 'Activity Created',
        details: `Activity "${activityName}" created. Activity confirmation email auto-triggered to dealer, requestor, and department lead. IO confirmation to be made.`,
      });

      logger.info(`[DealerClaimService] Activity Creation logged as activity for request ${requestId}. Activity creation completed.`);
    } catch (error) {
      logger.error(`[DealerClaimService] Error processing Step 4 activity creation for request ${requestId}:`, error);
      throw error;
    }
  }

  /**
   * Snapshot current claim state for version history before revisions
   */
  /**
   * Save proposal version history (Step 1)
   */
  async saveProposalHistory(
    requestId: string,
    approvalLevelId: string,
    levelNumber: number,
    changeReason: string,
    userId: string
  ): Promise<void> {
    try {
      const proposalDetails = await DealerProposalDetails.findOne({ where: { requestId } });
      if (!proposalDetails) {
        logger.warn(`[DealerClaimService] No proposal found for request ${requestId}, skipping history`);
        return;
      }

      const costItems = await DealerProposalCostItem.findAll({
        where: { proposalId: (proposalDetails as any).proposalId || (proposalDetails as any).proposal_id }
      });

      // Get level name from approval level
      const level = await ApprovalLevel.findByPk(approvalLevelId);
      const levelName = level?.levelName || undefined;

      // Get next version for this level (match by levelName for consistency)
      const lastVersion = await DealerClaimHistory.findOne({
        where: levelName ? {
          requestId,
          levelName,
          snapshotType: SnapshotType.PROPOSAL
        } : {
          requestId,
          levelNumber,
          snapshotType: SnapshotType.PROPOSAL
        },
        order: [['version', 'DESC']]
      });
      const nextVersion = lastVersion ? lastVersion.version + 1 : 1;

      // Store all proposal data in JSONB
      // Handle expectedCompletionDate - it might be a Date object, string, or null
      let expectedCompletionDateStr = null;
      if (proposalDetails.expectedCompletionDate) {
        if (proposalDetails.expectedCompletionDate instanceof Date) {
          expectedCompletionDateStr = proposalDetails.expectedCompletionDate.toISOString();
        } else if (typeof proposalDetails.expectedCompletionDate === 'string') {
          expectedCompletionDateStr = proposalDetails.expectedCompletionDate;
        }
      }

      // Fetch supporting documents
      const supportingDocs = await Document.findAll({
        where: {
          requestId,
          category: 'SUPPORTING',
          isDeleted: false
        },
        order: [['createdAt', 'DESC']]
      });

      const snapshotData = {
        documentUrl: proposalDetails.proposalDocumentUrl,
        totalBudget: Number(proposalDetails.totalEstimatedBudget || 0),
        comments: proposalDetails.dealerComments,
        expectedCompletionDate: expectedCompletionDateStr,
        costItems: costItems.map(i => ({
          description: i.itemDescription,
          amount: Number(i.amount || 0),
          order: i.itemOrder
        })),
        otherDocuments: supportingDocs.map(doc => ({
          documentId: doc.documentId,
          fileName: doc.fileName,
          originalFileName: doc.originalFileName,
          storageUrl: doc.storageUrl,
          uploadedAt: doc.uploadedAt
        }))
      };

      await DealerClaimHistory.create({
        requestId,
        approvalLevelId,
        levelNumber,
        levelName,
        version: nextVersion,
        snapshotType: SnapshotType.PROPOSAL,
        snapshotData,
        changeReason,
        changedBy: userId
      });

      logger.info(`[DealerClaimService] Saved proposal history (v${nextVersion}) for level ${levelNumber}, request ${requestId}`);
    } catch (error) {
      logger.error(`[DealerClaimService] Error saving proposal history for request ${requestId}:`, error);
    }
  }

  /**
   * Save completion version history (Step 4/5)
   */
  async saveCompletionHistory(
    requestId: string,
    approvalLevelId: string,
    levelNumber: number,
    changeReason: string,
    userId: string
  ): Promise<void> {
    try {
      const completionDetails = await DealerCompletionDetails.findOne({ where: { requestId } });
      if (!completionDetails) {
        logger.warn(`[DealerClaimService] No completion found for request ${requestId}, skipping history`);
        return;
      }

      const expenses = await DealerCompletionExpense.findAll({ where: { requestId } });

      // Get level name from approval level
      const level = await ApprovalLevel.findByPk(approvalLevelId);
      const levelName = level?.levelName || undefined;

      // Get next version for this level (match by levelName for consistency)
      const lastVersion = await DealerClaimHistory.findOne({
        where: levelName ? {
          requestId,
          levelName,
          snapshotType: SnapshotType.COMPLETION
        } : {
          requestId,
          levelNumber,
          snapshotType: SnapshotType.COMPLETION
        },
        order: [['version', 'DESC']]
      });
      const nextVersion = lastVersion ? lastVersion.version + 1 : 1;

      // Fetch supporting documents for completion
      const supportingDocs = await Document.findAll({
        where: {
          requestId,
          category: 'SUPPORTING',
          isDeleted: false
        },
        order: [['createdAt', 'DESC']]
      });

      // Store all completion data in JSONB
      const snapshotData = {
        documentUrl: (completionDetails as any).completionDocumentUrl || null,
        totalExpenses: Number(completionDetails.totalClosedExpenses || 0),
        comments: (completionDetails as any).completionDescription || null,
        expenses: expenses.map(e => ({
          description: e.description,
          amount: Number(e.amount || 0)
        })),
        otherDocuments: supportingDocs.map(doc => ({
          documentId: doc.documentId,
          fileName: doc.fileName,
          originalFileName: doc.originalFileName,
          storageUrl: doc.storageUrl,
          uploadedAt: doc.uploadedAt
        }))
      };

      await DealerClaimHistory.create({
        requestId,
        approvalLevelId,
        levelNumber,
        levelName,
        version: nextVersion,
        snapshotType: SnapshotType.COMPLETION,
        snapshotData,
        changeReason,
        changedBy: userId
      });

      logger.info(`[DealerClaimService] Saved completion history (v${nextVersion}) for level ${levelNumber}, request ${requestId}`);
    } catch (error) {
      logger.error(`[DealerClaimService] Error saving completion history for request ${requestId}:`, error);
    }
  }

  /**
   * Save internal order version history
   */
  async saveIOHistory(
    requestId: string,
    approvalLevelId: string,
    levelNumber: number,
    changeReason: string,
    userId: string
  ): Promise<void> {
    try {
      const internalOrder = await InternalOrder.findOne({ where: { requestId } });
      if (!internalOrder || !internalOrder.ioBlockedAmount || internalOrder.ioBlockedAmount <= 0) {
        logger.warn(`[DealerClaimService] No IO block found for request ${requestId}, skipping history`);
        return;
      }

      // Get level name from approval level
      const level = await ApprovalLevel.findByPk(approvalLevelId);
      const levelName = level?.levelName || undefined;

      // Get next version for this level (match by levelName for consistency)
      const lastVersion = await DealerClaimHistory.findOne({
        where: levelName ? {
          requestId,
          levelName,
          snapshotType: SnapshotType.INTERNAL_ORDER
        } : {
          requestId,
          levelNumber,
          snapshotType: SnapshotType.INTERNAL_ORDER
        },
        order: [['version', 'DESC']]
      });
      const nextVersion = lastVersion ? lastVersion.version + 1 : 1;

      // Store all IO data in JSONB
      const snapshotData = {
        ioNumber: internalOrder.ioNumber,
        blockedAmount: Number(internalOrder.ioBlockedAmount || 0),
        availableBalance: Number(internalOrder.ioAvailableBalance || 0),
        remainingBalance: Number(internalOrder.ioRemainingBalance || 0),
        sapDocumentNumber: internalOrder.sapDocumentNumber
      };

      await DealerClaimHistory.create({
        requestId,
        approvalLevelId,
        levelNumber,
        levelName,
        version: nextVersion,
        snapshotType: SnapshotType.INTERNAL_ORDER,
        snapshotData,
        changeReason,
        changedBy: userId
      });

      logger.info(`[DealerClaimService] Saved IO history (v${nextVersion}) for level ${levelNumber}, request ${requestId}`);
    } catch (error) {
      logger.error(`[DealerClaimService] Error saving IO history for request ${requestId}:`, error);
    }
  }

  /**
   * Save approval version history (for approver actions)
   */
  async saveApprovalHistory(
    requestId: string,
    approvalLevelId: string,
    levelNumber: number,
    action: 'APPROVE' | 'REJECT',
    comments: string,
    rejectionReason: string | undefined,
    userId: string
  ): Promise<void> {
    try {
      const level = await ApprovalLevel.findByPk(approvalLevelId);
      if (!level) {
        logger.warn(`[DealerClaimService] No approval level found for ${approvalLevelId}, skipping history`);
        return;
      }

      // Get next version for this level (match by levelName for consistency)
      const lastVersion = await DealerClaimHistory.findOne({
        where: level.levelName ? {
          requestId,
          levelName: level.levelName,
          snapshotType: SnapshotType.APPROVE
        } : {
          requestId,
          levelNumber,
          snapshotType: SnapshotType.APPROVE
        },
        order: [['version', 'DESC']]
      });
      const nextVersion = lastVersion ? lastVersion.version + 1 : 1;

      // Store approval data in JSONB
      const snapshotData = {
        action,
        comments: comments || undefined,
        rejectionReason: rejectionReason || undefined,
        approverName: level.approverName,
        approverEmail: level.approverEmail,
        levelName: level.levelName
      };

      // Build changeReason - will be updated later if moving to next level
      // For now, just include the basic approval/rejection info
      const changeReason = action === 'APPROVE'
        ? `Approved by ${level.approverName || level.approverEmail}`
        : `Rejected by ${level.approverName || level.approverEmail}`;

      await DealerClaimHistory.create({
        requestId,
        approvalLevelId,
        levelNumber,
        levelName: level.levelName || undefined,
        version: nextVersion,
        snapshotType: SnapshotType.APPROVE,
        snapshotData,
        changeReason,
        changedBy: userId
      });

      logger.info(`[DealerClaimService] Saved approval history (v${nextVersion}) for level ${levelNumber}, request ${requestId}`);
    } catch (error) {
      logger.error(`[DealerClaimService] Error saving approval history for request ${requestId}:`, error);
    }
  }

  /**
   * Save workflow-level version history (for actions that move workflow forward/backward)
   */
  async saveWorkflowHistory(
    requestId: string,
    changeReason: string,
    userId: string,
    approvalLevelId?: string,
    levelNumber?: number,
    levelName?: string,
    approvalComment?: string
  ): Promise<void> {
    try {
      const wf = await WorkflowRequest.findByPk(requestId);
      if (!wf) return;

      // Get next version for workflow-level snapshots PER LEVEL
      // Each level should have its own version numbering starting from 1
      // Filter by levelName or levelNumber to get versions for this specific level
      const lastVersion = await DealerClaimHistory.findOne({
        where: levelName ? {
          requestId,
          levelName,
          snapshotType: SnapshotType.WORKFLOW
        } : levelNumber !== undefined ? {
          requestId,
          levelNumber,
          snapshotType: SnapshotType.WORKFLOW
        } : {
          requestId,
          snapshotType: SnapshotType.WORKFLOW
        },
        order: [['version', 'DESC']]
      });
      const nextVersion = lastVersion ? lastVersion.version + 1 : 1;

      // Store workflow data in JSONB
      // Include level information for version tracking and comparison
      // Include approval comment if provided (for approval actions)
      const snapshotData: any = {
        status: wf.status,
        currentLevel: wf.currentLevel,
        // Include level info in snapshotData for completeness and version tracking
        approvalLevelId: approvalLevelId || undefined,
        levelNumber: levelNumber || undefined,
        levelName: levelName || undefined
      };

      // Add approval comment to snapshotData if provided
      if (approvalComment) {
        snapshotData.comments = approvalComment;
      }

      await DealerClaimHistory.create({
        requestId,
        approvalLevelId: approvalLevelId || undefined,
        levelNumber: levelNumber || undefined,
        levelName: levelName || undefined,
        version: nextVersion,
        snapshotType: SnapshotType.WORKFLOW,
        snapshotData,
        changeReason,
        changedBy: userId
      });

      logger.info(`[DealerClaimService] Saved workflow history (v${nextVersion}) for request ${requestId}, level ${levelNumber || 'N/A'}`);
    } catch (error) {
      logger.error(`[DealerClaimService] Error saving workflow history for request ${requestId}:`, error);
    }
  }

  /**
   * Create or activate initiator action level when request is rejected
   * This allows initiator to take action (REVISE, CANCEL, REOPEN) directly from the step card
   */
  async createOrActivateInitiatorLevel(
    requestId: string,
    userId: string
  ): Promise<ApprovalLevel | null> {
    try {
      const wf = await WorkflowRequest.findByPk(requestId);
      if (!wf) return null;

      // Check if initiator level already exists
      let initiatorLevel = await ApprovalLevel.findOne({
        where: {
          requestId,
          levelName: 'Initiator Action'
        }
      });

      if (initiatorLevel) {
        // Activate existing level
        await initiatorLevel.update({
          status: ApprovalStatus.IN_PROGRESS,
          levelStartTime: new Date(),
          tatStartTime: new Date(),
          approverId: wf.initiatorId
        });
        return initiatorLevel;
      }

      // Create new initiator level
      // Find the highest level number to place it after
      const maxLevel = await ApprovalLevel.findOne({
        where: { requestId },
        order: [['levelNumber', 'DESC']]
      });
      const nextLevelNumber = maxLevel ? maxLevel.levelNumber + 1 : 0;

      // Get initiator user details
      const initiatorUser = await User.findByPk(wf.initiatorId);
      if (!initiatorUser) {
        throw new Error('Initiator user not found');
      }

      initiatorLevel = await ApprovalLevel.create({
        requestId,
        levelNumber: nextLevelNumber,
        levelName: 'Initiator Action',
        approverId: wf.initiatorId,
        approverEmail: initiatorUser.email || '',
        approverName: initiatorUser.displayName || initiatorUser.email || 'Initiator',
        status: ApprovalStatus.IN_PROGRESS,
        levelStartTime: new Date(),
        tatStartTime: new Date(),
        tatHours: 0, // No TAT for initiator action
        elapsedHours: 0,
        remainingHours: 0,
        tatPercentageUsed: 0,
        isFinalApprover: false
      } as any);

      logger.info(`[DealerClaimService] Created/activated initiator level for request ${requestId}`);
      return initiatorLevel;
    } catch (error) {
      logger.error(`[DealerClaimService] Error creating/activating initiator level:`, error);
      return null;
    }
  }

  /**
   * @deprecated - Removed complex snapshot method. Snapshots are now taken at step execution.
   */
  async saveCompleteRevisionSnapshot_DEPRECATED(
    requestId: string,
    changeReason: string,
    userId: string
  ): Promise<void> {
    try {
      logger.info(`[DealerClaimService] Capturing complete revision snapshot for request ${requestId}`);

      // 1. Capture current proposal snapshot (if exists)
      const proposalDetails = await DealerProposalDetails.findOne({ where: { requestId } });
      if (proposalDetails) {
        const costItems = await DealerProposalCostItem.findAll({
          where: { proposalId: (proposalDetails as any).proposalId || (proposalDetails as any).proposal_id }
        });

        // Find dealer proposal level
        const dealerLevel = await ApprovalLevel.findOne({
          where: {
            requestId,
            levelName: 'Dealer Proposal Submission'
          }
        }) || await ApprovalLevel.findOne({
          where: { requestId, levelNumber: 1 }
        });

        if (dealerLevel) {
          const proposalSnapshotData = {
            documentUrl: proposalDetails.proposalDocumentUrl,
            totalBudget: Number(proposalDetails.totalEstimatedBudget || 0),
            comments: proposalDetails.dealerComments,
            expectedCompletionDate: proposalDetails.expectedCompletionDate ? proposalDetails.expectedCompletionDate.toISOString() : null,
            costItems: costItems.map(i => ({
              description: i.itemDescription,
              amount: Number(i.amount || 0),
              order: i.itemOrder
            }))
          };

          // Get next version for this level
          const lastProposalVersion = await DealerClaimHistory.findOne({
            where: {
              requestId,
              levelName: dealerLevel.levelName || undefined,
              snapshotType: SnapshotType.PROPOSAL
            },
            order: [['version', 'DESC']]
          });
          const nextProposalVersion = lastProposalVersion ? lastProposalVersion.version + 1 : 1;

          await DealerClaimHistory.create({
            requestId,
            approvalLevelId: dealerLevel.levelId,
            levelNumber: dealerLevel.levelNumber,
            levelName: dealerLevel.levelName || undefined,
            version: nextProposalVersion,
            snapshotType: SnapshotType.PROPOSAL,
            snapshotData: proposalSnapshotData,
            changeReason: `${changeReason} - Pre-revision snapshot`,
            changedBy: userId
          });

          logger.info(`[DealerClaimService] Captured proposal snapshot (v${nextProposalVersion}) for revision`);
        }
      }

      // 2. Capture current completion snapshot (if exists)
      const completionDetails = await DealerCompletionDetails.findOne({ where: { requestId } });
      if (completionDetails) {
        const expenses = await DealerCompletionExpense.findAll({
          where: { completionId: (completionDetails as any).completionId || (completionDetails as any).completion_id }
        });

        // Find completion level
        const completionLevel = await ApprovalLevel.findOne({
          where: {
            requestId,
            levelName: 'Dealer Completion Documents'
          }
        }) || await ApprovalLevel.findOne({
          where: { requestId, levelNumber: 4 }
        });

        if (completionLevel) {
          const completionSnapshotData = {
            documentUrl: (completionDetails as any).completionDocumentUrl || null,
            totalExpenses: Number(completionDetails.totalClosedExpenses || 0),
            comments: (completionDetails as any).completionDescription || null,
            expenses: expenses.map(e => ({
              description: e.description,
              amount: Number(e.amount || 0)
            }))
          };

          // Get next version for this level
          const lastCompletionVersion = await DealerClaimHistory.findOne({
            where: {
              requestId,
              levelName: completionLevel.levelName || undefined,
              snapshotType: SnapshotType.COMPLETION
            },
            order: [['version', 'DESC']]
          });
          const nextCompletionVersion = lastCompletionVersion ? lastCompletionVersion.version + 1 : 1;

          await DealerClaimHistory.create({
            requestId,
            approvalLevelId: completionLevel.levelId,
            levelNumber: completionLevel.levelNumber,
            levelName: completionLevel.levelName || undefined,
            version: nextCompletionVersion,
            snapshotType: SnapshotType.COMPLETION,
            snapshotData: completionSnapshotData,
            changeReason: `${changeReason} - Pre-revision snapshot`,
            changedBy: userId
          });

          logger.info(`[DealerClaimService] Captured completion snapshot (v${nextCompletionVersion}) for revision`);
        }
      }

      // 3. Capture current IO snapshot (if exists)
      const internalOrder = await InternalOrder.findOne({ where: { requestId } });
      if (internalOrder && internalOrder.ioBlockedAmount && internalOrder.ioBlockedAmount > 0) {
        const ioLevel = await ApprovalLevel.findOne({
          where: {
            requestId,
            levelName: 'Department Lead IO Approval'
          }
        }) || await ApprovalLevel.findOne({
          where: { requestId, levelNumber: 3 }
        });

        if (ioLevel) {
          const ioSnapshotData = {
            ioNumber: internalOrder.ioNumber,
            blockedAmount: Number(internalOrder.ioBlockedAmount || 0),
            availableBalance: Number(internalOrder.ioAvailableBalance || 0),
            remainingBalance: Number(internalOrder.ioRemainingBalance || 0),
            sapDocumentNumber: internalOrder.sapDocumentNumber
          };

          // Get next version for this level
          const lastIOVersion = await DealerClaimHistory.findOne({
            where: {
              requestId,
              levelName: ioLevel.levelName || undefined,
              snapshotType: SnapshotType.INTERNAL_ORDER
            },
            order: [['version', 'DESC']]
          });
          const nextIOVersion = lastIOVersion ? lastIOVersion.version + 1 : 1;

          await DealerClaimHistory.create({
            requestId,
            approvalLevelId: ioLevel.levelId,
            levelNumber: ioLevel.levelNumber,
            levelName: ioLevel.levelName || undefined,
            version: nextIOVersion,
            snapshotType: SnapshotType.INTERNAL_ORDER,
            snapshotData: ioSnapshotData,
            changeReason: `${changeReason} - Pre-revision snapshot`,
            changedBy: userId
          });

          logger.info(`[DealerClaimService] Captured IO snapshot (v${nextIOVersion}) for revision`);
        }
      }

      // 4. Capture ALL approval comments from all levels (so approvers can see their previous comments)
      const allLevels = await ApprovalLevel.findAll({
        where: { requestId },
        order: [['levelNumber', 'ASC']]
      });

      for (const level of allLevels) {
        // Only capture if level has been acted upon (has comments or action date)
        if (level.comments || level.actionDate || level.status === ApprovalStatus.APPROVED || level.status === ApprovalStatus.REJECTED) {
          const approver = level.approverId ? await User.findByPk(level.approverId) : null;

          const approvalSnapshotData = {
            action: level.status === ApprovalStatus.APPROVED ? 'APPROVE' : level.status === ApprovalStatus.REJECTED ? 'REJECT' : 'PENDING',
            comments: level.comments || undefined,
            rejectionReason: level.status === ApprovalStatus.REJECTED ? (level.comments || undefined) : undefined,
            approverName: approver?.displayName || approver?.email || undefined,
            approverEmail: approver?.email || undefined,
            levelName: level.levelName || undefined
          };

          // Get next version for this level's approval snapshot
          const lastApprovalVersion = await DealerClaimHistory.findOne({
            where: {
              requestId,
              levelName: level.levelName || undefined,
              snapshotType: SnapshotType.APPROVE
            },
            order: [['version', 'DESC']]
          });
          const nextApprovalVersion = lastApprovalVersion ? lastApprovalVersion.version + 1 : 1;

          await DealerClaimHistory.create({
            requestId,
            approvalLevelId: level.levelId,
            levelNumber: level.levelNumber,
            levelName: level.levelName || undefined,
            version: nextApprovalVersion,
            snapshotType: SnapshotType.APPROVE,
            snapshotData: approvalSnapshotData,
            changeReason: `${changeReason} - Pre-revision approval snapshot`,
            changedBy: userId
          });

          logger.info(`[DealerClaimService] Captured approval snapshot (v${nextApprovalVersion}) for level ${level.levelNumber} (${level.levelName})`);
        }
      }

      // 5. Save workflow-level snapshot
      const wf = await WorkflowRequest.findByPk(requestId);
      if (wf) {
        const lastWorkflowVersion = await DealerClaimHistory.findOne({
          where: {
            requestId,
            snapshotType: SnapshotType.WORKFLOW
          },
          order: [['version', 'DESC']]
        });
        const nextWorkflowVersion = lastWorkflowVersion ? lastWorkflowVersion.version + 1 : 1;

        await DealerClaimHistory.create({
          requestId,
          version: nextWorkflowVersion,
          snapshotType: SnapshotType.WORKFLOW,
          snapshotData: {
            status: wf.status,
            currentLevel: wf.currentLevel
          },
          changeReason: `${changeReason} - Pre-revision workflow snapshot`,
          changedBy: userId
        });

        logger.info(`[DealerClaimService] Captured workflow snapshot (v${nextWorkflowVersion}) for revision`);
      }

      logger.info(`[DealerClaimService] Complete revision snapshot captured for request ${requestId}`);
    } catch (error) {
      logger.error(`[DealerClaimService] Error saving complete revision snapshot for request ${requestId}:`, error);
      // Don't throw - we want to continue even if snapshot fails
    }
  }

  /**
   * Handle initiator actions when a request is in RETURNED status
   */
  async handleInitiatorAction(
    requestId: string,
    userId: string,
    action: 'REOPEN' | 'DISCUSS' | 'REVISE' | 'CANCEL',
    data?: { reason: string }
  ): Promise<void> {
    const wf = await WorkflowRequest.findByPk(requestId);
    if (!wf) throw new Error('Request not found');

    // Check if the current user is the initiator
    if (wf.initiatorId !== userId) {
      throw new Error('Only the initiator can perform actions on a rejected/returned request');
    }

    // A returned request is REJECTED but has NO closureDate
    if (wf.status !== WorkflowStatus.REJECTED || wf.closureDate) {
      throw new Error(`Request is in ${wf.status} status (Closed: ${!!wf.closureDate}), expected an open REJECTED state to perform this action`);
    }

    const initiator = await User.findByPk(userId);
    const initiatorName = initiator?.displayName || initiator?.email || 'Initiator';
    const now = new Date();

    switch (action) {
      case 'CANCEL': {
        // Format change reason to include the comment if provided
        const changeReason = data?.reason && data.reason.trim()
          ? `Request Cancelled: ${data.reason.trim()}`
          : 'Request Cancelled';

        // Find current level for workflow history
        const currentLevel = await ApprovalLevel.findOne({
          where: { requestId, levelNumber: wf.currentLevel || 1 }
        });

        await wf.update({
          status: WorkflowStatus.CLOSED,
          closureDate: now
        });

        await activityService.log({
          requestId,
          type: 'status_change',
          user: { userId, name: initiatorName },
          timestamp: now.toISOString(),
          action: 'Request Cancelled',
          details: data?.reason && data.reason.trim()
            ? `Request was cancelled by initiator. Reason: ${data.reason.trim()}`
            : 'Request was cancelled by initiator.'
        });
        break;
      }

      case 'REOPEN': {
        // Format change reason to include the comment if provided
        const changeReason = data?.reason && data.reason.trim()
          ? `Request Reopened: ${data.reason.trim()}`
          : 'Request Reopened';

        // Find Department Lead level dynamically (handles step shifts)
        const approvalsReopen = await ApprovalLevel.findAll({ where: { requestId } });
        const deptLeadLevel = approvalsReopen.find(l => {
          const name = (l.levelName || '').toLowerCase();
          return name.includes('department lead') || name.includes('dept lead') || l.levelNumber === 3;
        });

        if (!deptLeadLevel) {
          throw new Error('Department Lead approval level not found for this request');
        }

        const deptLeadLevelNumber = deptLeadLevel.levelNumber;

        // Move back to Department Lead Approval level FIRST
        await wf.update({
          status: WorkflowStatus.PENDING,
          currentLevel: deptLeadLevelNumber
        });

        // Capture workflow snapshot AFTER workflow update succeeds
        try {
          await this.saveWorkflowHistory(
            requestId,
            `Reopened and moved to Department Lead level (${deptLeadLevelNumber}) - ${changeReason}`,
            userId,
            deptLeadLevel.levelId,
            deptLeadLevelNumber,
            deptLeadLevel.levelName || undefined
          );
        } catch (snapshotError) {
          // Log error but don't fail the reopen - snapshot is for audit, not critical
          logger.error(`[DealerClaimService] Failed to save workflow history snapshot (non-critical):`, snapshotError);
        }

        // Reset the found level status to IN_PROGRESS so Dept Lead can approve again
        await deptLeadLevel.update({
          status: ApprovalStatus.IN_PROGRESS,
          levelStartTime: now,
          tatStartTime: now,
          actionDate: undefined,
          comments: undefined
        });

        await activityService.log({
          requestId,
          type: 'approval',
          user: { userId, name: initiatorName },
          timestamp: now.toISOString(),
          action: 'Request Reopened',
          details: data?.reason && data.reason.trim()
            ? `Initiator reopened the request for Department Lead approval. Reason: ${data.reason.trim()}`
            : 'Initiator reopened the request for Department Lead approval.'
        });

        if (deptLeadLevel.approverId) {
          await notificationService.sendToUsers([deptLeadLevel.approverId], {
            title: `Request Reopened: ${wf.requestNumber}`,
            body: `Initiator has reopened the request "${wf.title}" after revision/discussion.`,
            requestNumber: wf.requestNumber,
            requestId: wf.requestId,
            url: `/request/${wf.requestNumber}`,
            type: 'assignment',
            priority: 'HIGH',
            actionRequired: true
          });
        }
        break;
      }

      case 'DISCUSS': {
        // Format change reason to include the comment if provided
        const changeReason = data?.reason && data.reason.trim()
          ? `Discussion Requested: ${data.reason.trim()}`
          : 'Discussion Requested';

        // Find Dealer level dynamically
        const approvalsDiscuss = await ApprovalLevel.findAll({ where: { requestId } });
        const dealerLevelDiscuss = approvalsDiscuss.find(l => {
          const name = (l.levelName || '').toLowerCase();
          return name.includes('dealer proposal') || l.levelNumber === 1;
        });

        // Note: DISCUSS action doesn't change workflow state, so no snapshot needed
        // The action is logged in activity log only

        await activityService.log({
          requestId,
          type: 'status_change',
          user: { userId, name: initiatorName },
          timestamp: now.toISOString(),
          action: 'Discuss with Dealer',
          details: data?.reason && data.reason.trim()
            ? `Initiator indicated they will discuss with the dealer. Reason: ${data.reason.trim()}`
            : 'Initiator indicated they will discuss with the dealer.'
        });

        if (dealerLevelDiscuss?.approverId) {
          await notificationService.sendToUsers([dealerLevelDiscuss.approverId], {
            title: `Discussion Requested: ${wf.requestNumber}`,
            body: `The initiator of request "${wf.title}" wants to discuss the proposal with you.`,
            requestNumber: wf.requestNumber,
            requestId: wf.requestId,
            url: `/request/${wf.requestNumber}`,
            type: 'info',
            priority: 'MEDIUM'
          });
        }
        break;
      }

      case 'REVISE': {
        // Format change reason
        const changeReason = data?.reason && data.reason.trim()
          ? `Revision Requested: ${data.reason.trim()}`
          : 'Revision Requested';

        // Find current level and previous level
        const allLevels = await ApprovalLevel.findAll({
          where: { requestId },
          order: [['levelNumber', 'ASC']]
        });

        const currentLevelNumber = wf.currentLevel || 1;
        const currentLevel = allLevels.find(l => l.levelNumber === currentLevelNumber);

        if (!currentLevel) {
          throw new Error('Current approval level not found');
        }

        // Find previous level (the one before current)
        const previousLevel = allLevels.find(l => l.levelNumber < currentLevelNumber);

        if (!previousLevel) {
          throw new Error('No previous level found to revise to');
        }

        // Move back to previous level FIRST
        await wf.update({
          status: WorkflowStatus.PENDING,
          currentLevel: previousLevel.levelNumber
        });

        // Capture workflow snapshot AFTER workflow update succeeds
        try {
          await this.saveWorkflowHistory(
            requestId,
            `Moved back to previous level (${previousLevel.levelNumber}) - ${changeReason}`,
            userId,
            previousLevel.levelId,
            previousLevel.levelNumber,
            previousLevel.levelName || undefined
          );
        } catch (snapshotError) {
          // Log error but don't fail the revise - snapshot is for audit, not critical
          logger.error(`[DealerClaimService] Failed to save workflow history snapshot (non-critical):`, snapshotError);
        }

        // Reset current level to PENDING
        await currentLevel.update({
          status: ApprovalStatus.PENDING,
          actionDate: undefined,
          levelStartTime: undefined,
          levelEndTime: undefined,
          tatStartTime: undefined,
          elapsedHours: 0,
          tatPercentageUsed: 0,
          comments: undefined
        });

        // Activate previous level
        await previousLevel.update({
          status: ApprovalStatus.IN_PROGRESS,
          levelStartTime: now,
          tatStartTime: now,
          comments: changeReason, // Save revision reason as comment
          actionDate: undefined,
          levelEndTime: undefined,
          elapsedHours: 0,
          tatPercentageUsed: 0
        });

        await activityService.log({
          requestId,
          type: 'assignment',
          user: { userId, name: initiatorName },
          timestamp: now.toISOString(),
          action: 'Revision Requested',
          details: data?.reason && data.reason.trim()
            ? `Initiator requested revision. Moving back to previous step. Reason: ${data.reason.trim()}`
            : 'Initiator requested revision. Moving back to previous step.'
        });

        // Notify the approver of the previous level
        if (previousLevel.approverId) {
          await notificationService.sendToUsers([previousLevel.approverId], {
            title: `Revision Required: ${wf.requestNumber}`,
            body: `Initiator has requested a revision for request "${wf.title}". The request has been moved back to your level.`,
            requestNumber: wf.requestNumber,
            requestId: wf.requestId,
            url: `/request/${wf.requestNumber}`,
            type: 'assignment',
            priority: 'HIGH',
            actionRequired: true
          });
        }
        break;
      }
    }

    const { emitToRequestRoom } = await import('../realtime/socket');
    emitToRequestRoom(requestId, 'request:updated', {
      requestId,
      requestNumber: wf.requestNumber,
      action: `INITIATOR_${action}`,
      timestamp: now.toISOString()
    });
  }

  async getHistory(requestId: string): Promise<any[]> {
    const history = await DealerClaimHistory.findAll({
      where: { requestId },
      order: [['version', 'DESC']],
      include: [
        {
          model: User,
          as: 'changer',
          attributes: ['userId', 'displayName', 'email']
        }
      ]
    });

    // Map to plain objects and sort otherDocuments in snapshots
    return history.map(item => {
      const plain = item.get({ plain: true });
      if (plain.snapshotData && plain.snapshotData.otherDocuments && Array.isArray(plain.snapshotData.otherDocuments)) {
        plain.snapshotData.otherDocuments.sort((a: any, b: any) => {
          const dateA = a.uploadedAt ? new Date(a.uploadedAt).getTime() : 0;
          const dateB = b.uploadedAt ? new Date(b.uploadedAt).getTime() : 0;
          return dateB - dateA;
        });
      }
      return plain;
    });
  }
}

