import { Response, NextFunction } from 'express'; // Triggering reload to pick up constants changes
import db from '../../database/models/index.js';
import logger from '../../common/utils/logger.js';
import {
    RESIGNATION_STAGES,
    AUDIT_ACTIONS,
    ROLES,
    REQUEST_TYPES,
    FNF_STATUS,
    RESIGNATION_DOCUMENT_TYPES,
    RESIGNATION_DOCUMENT_STAGES
} from '../../common/config/constants.js';
import { Op, Transaction } from 'sequelize';
import { AuthRequest } from '../../types/express.types.js';
import ExternalMocksService from '../../common/utils/externalMocks.service.js';

import { ResignationWorkflowService } from '../../services/ResignationWorkflowService.js';
import { NomenclatureService } from '../../common/utils/nomenclature.js';
import { ParticipantService } from '../../services/ParticipantService.js';
import { notifyResignationSubmittedEmails, notifyStakeholdersOnTransition } from '../../common/utils/workflow-email-notifications.js';
import { getResignationStatusForStage, normalizeClearanceStatus } from '../../common/utils/offboardingStatus.js';
import { resolveEntityUuidByType } from '../../common/utils/requestResolver.js';
import { OFFBOARDING_ACTIONS } from '../../common/config/constants.js';
import { validateOffboardingAction, getPreviousStage } from '../../common/utils/offboardingWorkflow.utils.js';
import { writeWorkflowActivityWorknote } from '../../common/utils/workflowWorknote.js';
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';
import { sendEmail } from '../../common/utils/email.service.js';
import fs from 'fs';
import path from 'path';

// Removed generateResignationId and moved to NomenclatureService
const resolveResignationUuid = async (id: string) => {
    const { resolvedId } = await resolveEntityUuidByType(db as any, id, 'resignation');
    return resolvedId;
};

// Create resignation request (Dealer only)
export const createResignation = async (req: AuthRequest, res: Response, next: NextFunction) => {
    const transaction: Transaction = await db.sequelize.transaction();
    try {
        if (!req.user) throw new Error('Unauthorized');
        const { outletId, resignationType, lastOperationalDateSales, lastOperationalDateServices, reason, additionalInfo } = req.body;
        const userRole = req.user.roleCode || req.user.role;
        const isInternalInitiator = [ROLES.ASM, ROLES.DD_ADMIN, ROLES.SUPER_ADMIN].includes(userRole as any);

        let dealerId: string;
        let outlet: any;

        if (isInternalInitiator) {
            // Internal initiator (ASM/Admin) selects the outlet
            outlet = await db.Outlet.findOne({ where: { id: outletId } });
            if (!outlet) {
                await transaction.rollback();
                return res.status(404).json({ success: false, message: 'Outlet not found' });
            }
            dealerId = outlet.dealerId;
        } else {
            // Dealer (Self-Service) initiates for their own outlet
            dealerId = req.user.id;
            outlet = await db.Outlet.findOne({ where: { id: outletId, dealerId } });
            if (!outlet) {
                await transaction.rollback();
                return res.status(404).json({ success: false, message: 'Outlet not found or does not belong to you' });
            }
        }

        const existingResignation = await db.Resignation.findOne({
            where: { outletId, status: { [Op.notIn]: ['Completed', 'Rejected', 'Withdrawn', 'Revoked'] } }
        });
        if (existingResignation) {
            await transaction.rollback();
            return res.status(400).json({ success: false, message: 'This outlet already has an active resignation request' });
        }

        const { FNF_DEPARTMENTS } = await import('../../common/config/constants.js');
        const initialClearances: Record<string, any> = {};
        FNF_DEPARTMENTS.forEach(dept => {
            initialClearances[dept] = { status: 'Pending', remarks: '', amount: 0, type: 'Receivable' };
        });

        const resignationId = await NomenclatureService.generateResignationId();
        const resignation = await db.Resignation.create({
            resignationId,
            outletId,
            dealerId,
            resignationType,
            lastOperationalDateSales,
            lastOperationalDateServices,
            reason,
            additionalInfo,
            currentStage: RESIGNATION_STAGES.ASM,
            status: getResignationStatusForStage(RESIGNATION_STAGES.ASM),
            progressPercentage: ResignationWorkflowService.calculateProgress(RESIGNATION_STAGES.ASM),
            submittedOn: new Date(),
            documents: [],
            departmentalClearances: initialClearances,
            timeline: [{
                stage: 'Request Submitted',
                timestamp: new Date(),
                user: req.user.fullName,
                action: isInternalInitiator ? 'Resignation initiated by ASM' : 'Resignation request submitted by dealer',
                remarks: reason || ''
            }]
        }, { transaction });

        await outlet.update({ status: 'Pending Resignation' }, { transaction });
        await db.ResignationAudit.create({
            userId: req.user.id,
            action: AUDIT_ACTIONS.CREATED,
            resignationId: resignation.id,
            remarks: isInternalInitiator ? 'ASM initiated resignation request' : 'Dealer submitted resignation request'
        }, { transaction });

        await transaction.commit();
        logger.info(`Resignation request created: ${resignationId} by dealer: ${req.user.email}`);

        try {
            await ParticipantService.assignResignationParticipants(resignation.id);
            await notifyResignationSubmittedEmails(resignation.toJSON ? resignation.toJSON() : resignation);
        } catch (partErr) {
            logger.error('Error assigning resignation participants or submit emails:', partErr);
        }

        res.status(201).json({ success: true, message: 'Resignation request submitted successfully', resignationId, resignation });
    } catch (error) {
        if (transaction) await transaction.rollback();
        logger.error('Error creating resignation:', error);
        next(error);
    }
};

// Get all resignation requests
export const getResignations = async (req: AuthRequest, res: Response, next: NextFunction) => {
    try {
        if (!req.user) throw new Error('Unauthorized');
        const where: any = {};

        if (req.user.roleCode === ROLES.DEALER) {
            where.dealerId = req.user.id;
        } else {
            // For administrative users, filter by status or assignment if requested
            const { status, onlyMine } = req.query;

            if (status) {
                if (String(status).includes(',')) {
                    where.status = { [Op.in]: String(status).split(',') };
                } else if (status === 'open') {
                    where.status = { [Op.notIn]: ['Completed', 'Closed', 'Rejected'] };
                } else {
                    where.status = status;
                }
            }

            if (onlyMine === 'true') {
                // This would involve a subquery on RequestParticipants or assignedTo field
                // Assuming currentStage context or RequestParticipants
                where.currentStage = { [Op.like]: `%${req.user.roleCode}%` };
            }
        }

        const page = parseInt(req.query.page as string) || 1;
        const limit = parseInt(req.query.limit as string) || 10;
        const offset = (page - 1) * limit;

        const { count, rows: resignations } = await db.Resignation.findAndCountAll({
            where,
            include: [
                { model: db.Outlet, as: 'outlet' },
                {
                    model: db.User,
                    as: 'dealer',
                    attributes: ['fullName'],
                    include: [
                        { model: db.Dealer, as: 'dealerProfile', include: [{ model: db.DealerCode, as: 'dealerCode' }] }
                    ]
                }
            ],
            order: [['createdAt', 'DESC']],
            limit,
            offset,
            distinct: true
        });
        res.json({
            success: true,
            resignations,
            meta: {
                total: count,
                totalPages: Math.ceil(count / limit),
                currentPage: page,
                limit,
                stats: {
                    total: count,
                    open: await db.Resignation.count({ where: { ...where, status: { [Op.notIn]: ['Completed', 'Closed', 'Rejected'] } } }),
                    completed: await db.Resignation.count({ where: { ...where, status: { [Op.in]: ['Completed', 'Closed'] } } })
                }
            }
        });
    } catch (error) {
        logger.error('Error fetching resignations:', error);
        next(error);
    }
};

// Get resignation by ID
export const getResignationById = async (req: AuthRequest, res: Response, next: NextFunction) => {
    try {
        const { id } = req.params;
        const resolvedId = await resolveResignationUuid(String(id));

        const resignation = await db.Resignation.findOne({
            where: { id: resolvedId },
            include: [
                { model: db.Outlet, as: 'outlet' },
                {
                    model: db.User,
                    as: 'dealer',
                    attributes: ['id', 'fullName', 'email', 'roleCode'],
                    include: [
                        {
                            model: db.Dealer,
                            as: 'dealerProfile',
                            include: [
                                { model: db.DealerCode, as: 'dealerCode' },
                                {
                                    model: db.Application,
                                    as: 'application',
                                    include: [
                                        { model: db.District, as: 'district' },
                                        { model: db.LoiRequest, as: 'loiRequests', where: { status: 'approved' }, required: false },
                                        { model: db.LoaRequest, as: 'loaRequests', where: { status: 'approved' }, required: false }
                                    ]
                                }
                            ]
                        }
                    ]
                },
                {
                    model: db.ResignationDocument,
                    as: 'uploadedDocuments',
                    include: [{ model: db.User, as: 'uploader', attributes: ['fullName'] }]
                },
                {
                    model: db.FnF,
                    as: 'settlement',
                    include: [
                        { model: db.FnFLineItem, as: 'lineItems' },
                        { model: db.FffClearance, as: 'clearances' }
                    ]
                },
                {
                    model: db.RequestParticipant,
                    as: 'participants',
                    include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName', 'email', 'roleCode'] }]
                }
            ]
        });

        if (!resignation) {
            return res.status(404).json({ success: false, message: 'Resignation not found' });
        }
        res.json({ success: true, resignation });
    } catch (error) {
        logger.error('Error fetching resignation:', error);
        next(error);
    }
};

export const uploadResignationDocument = async (req: AuthRequest, res: Response, next: NextFunction) => {
    const transaction: Transaction = await db.sequelize.transaction();
    try {
        if (!req.user) throw new Error('Unauthorized');
        if (!req.file) {
            await transaction.rollback();
            return res.status(400).json({ success: false, message: 'File is required' });
        }

        const { id } = req.params;
        const { documentType = RESIGNATION_DOCUMENT_TYPES[0], stage = null } = req.body;
        if (!RESIGNATION_DOCUMENT_TYPES.includes(documentType)) {
            await transaction.rollback();
            return res.status(400).json({
                success: false,
                message: `Invalid document type. Allowed values: ${RESIGNATION_DOCUMENT_TYPES.join(', ')}`
            });
        }
        if (stage && !RESIGNATION_DOCUMENT_STAGES.includes(stage)) {
            await transaction.rollback();
            return res.status(400).json({
                success: false,
                message: `Invalid stage. Allowed values: ${RESIGNATION_DOCUMENT_STAGES.join(', ')}`
            });
        }
        const resolvedId = await resolveResignationUuid(String(id));
        const resignation = await db.Resignation.findOne({
            where: { id: resolvedId }
        });

        if (!resignation) {
            await transaction.rollback();
            return res.status(404).json({ success: false, message: 'Resignation not found' });
        }

        const filePath = `/uploads/documents/${req.file.filename}`;
        const document = await db.ResignationDocument.create({
            resignationId: resignation.id,
            documentType,
            fileName: req.file.originalname,
            filePath,
            fileSize: req.file.size,
            mimeType: req.file.mimetype,
            stage,
            uploadedBy: req.user.id
        }, { transaction });

        await db.ResignationAudit.create({
            userId: req.user.id,
            resignationId: resignation.id,
            action: AUDIT_ACTIONS.DOCUMENT_UPLOADED,
            remarks: `${documentType} uploaded`,
            details: { fileName: req.file.originalname, stage, documentType }
        }, { transaction });

        const timeline = [...(resignation.timeline || []), {
            stage: resignation.currentStage,
            timestamp: new Date(),
            user: req.user.fullName,
            action: `Document uploaded: ${documentType}`,
            remarks: req.file.originalname
        }];
        await resignation.update({ timeline }, { transaction });

        await transaction.commit();

        // SRS §7.5.2 — When Legal uploads the acceptance letter, notify DD-Admin and ASM
        // so they can communicate official closure to the dealer (field hierarchy).
        if (stage === RESIGNATION_STAGES.LEGAL && resignation.currentStage === RESIGNATION_STAGES.LEGAL) {
            const portalBase = getFrontendBaseUrl();
            const resignationCode = resignation.resignationId || resignation.id;
            setImmediate(() =>
                notifyStakeholdersOnTransition(
                    resignation.id,
                    REQUEST_TYPES.RESIGNATION,
                    'Resignation Legal Closure',   // synthetic stage key mapped to DD_ADMIN + ASM in resolveNextActors
                    {
                        code: resignationCode,
                        dealerName: 'Dealer',          // enriched below if needed
                        dealerId: resignation.dealerId,
                        actionUserFullName: req.user!.fullName || 'Legal Team',
                        action: 'Legal acceptance letter uploaded — ready for DD-Admin review and ASM communication',
                        remarks: `Document: ${req.file!.originalname}`,
                        link: `${portalBase}/dealer-resignation/${resignation.id}`
                    }
                ).catch((err: any) => logger.error('[uploadResignationDocument] legal-closure notify:', err))
            );
        }

        res.status(201).json({ success: true, message: 'Document uploaded successfully', document });
    } catch (error) {
        await transaction.rollback();
        logger.error('Error uploading resignation document:', error);
        next(error);
    }
};

// Approve resignation (move to next stage)
export const approveResignation = async (req: AuthRequest, res: Response, next: NextFunction) => {
    const targetOverride = (req as any).targetStage;

    const transaction: Transaction = await db.sequelize.transaction();
    try {
        if (!req.user) throw new Error('Unauthorized');
        const { id } = req.params;
        const { remarks } = req.body;
        const resolvedId = await resolveResignationUuid(String(id));

        const resignation = await db.Resignation.findOne({
            where: { id: resolvedId },
            include: [
                { model: db.Outlet, as: 'outlet' },
                { model: db.User, as: 'dealer', attributes: ['id', 'dealerId'] }
            ]
        });
        if (!resignation) {
            await transaction.rollback();
            return res.status(404).json({ success: false, message: 'Resignation not found' });
        }

        // 1. Authorization Check (Skip if targetOverride is from pushfnf, handled in updateResignationStatus)
        if (!targetOverride) {
            const isAuthorized = await ResignationWorkflowService.canUserAction(resignation, req.user);
            if (!isAuthorized) {
                await transaction.rollback();
                return res.status(403).json({ success: false, message: `You are not authorized to approve this request at the ${resignation.currentStage} stage` });
            }
        }

        const stageFlow: Record<string, string> = {
            [RESIGNATION_STAGES.ASM]: RESIGNATION_STAGES.RBM,
            [RESIGNATION_STAGES.RBM]: RESIGNATION_STAGES.ZBH,
            [RESIGNATION_STAGES.ZBH]: RESIGNATION_STAGES.DD_LEAD,
            [RESIGNATION_STAGES.DD_LEAD]: RESIGNATION_STAGES.DD_HEAD,
            [RESIGNATION_STAGES.DD_HEAD]: RESIGNATION_STAGES.NBH,
            [RESIGNATION_STAGES.NBH]: RESIGNATION_STAGES.LEGAL,
            [RESIGNATION_STAGES.LEGAL]: RESIGNATION_STAGES.DD_ADMIN,
            // DD Admin approval completes internal review; F&F is started only via explicit Push to F&F.
            [RESIGNATION_STAGES.DD_ADMIN]: RESIGNATION_STAGES.AWAITING_FNF,
            [RESIGNATION_STAGES.FNF_INITIATED]: RESIGNATION_STAGES.COMPLETED
        };

        const nextStage = targetOverride || stageFlow[resignation.currentStage];
        if (!nextStage) {
            await transaction.rollback();
            return res.status(400).json({ success: false, message: 'Cannot move to next stage from current state' });
        }

        // F&F records are created only from explicit Push to F&F (targetStage), not from sequential approvals.
        // LWD gate applies to that manual push (SRS §4.2.2.9).
        let shouldTriggerFnF = false;
        if (nextStage === RESIGNATION_STAGES.FNF_INITIATED && targetOverride) {
            const lwdString = resignation.lastOperationalDateServices || resignation.lastOperationalDateSales;
            const { force } = req.body;
            const { isLwdReached, formatLwdDisplay } = await import('../../common/utils/offboardingLwd.js');

            if (!force && lwdString && !isLwdReached(lwdString)) {
                await transaction.rollback();
                return res.status(400).json({
                    success: false,
                    message: `F&F settlement process is initiated only on the Last Working Day (${formatLwdDisplay(lwdString)}) of the dealership.`,
                    canForce: true
                });
            }
            shouldTriggerFnF = true;
        }

        // Sequence guard: resignation can be marked completed only after F&F settlement is complete.
        if (
            resignation.currentStage === RESIGNATION_STAGES.FNF_INITIATED &&
            nextStage === RESIGNATION_STAGES.COMPLETED
        ) {
            const fnf = await db.FnF.findOne({ where: { resignationId: resignation.id }, transaction });
            if (!fnf || fnf.status !== FNF_STATUS.COMPLETED) {
                await transaction.rollback();
                return res.status(400).json({
                    success: false,
                    message: 'Cannot complete resignation. F&F settlement must be completed first.'
                });
            }
        }

        const sourceStage = resignation.currentStage;

        // JOINT APPROVAL LOGIC FOR RBM STAGE
        if (sourceStage === RESIGNATION_STAGES.RBM) {
            // Log the current user's approval in audit
            await db.ResignationAudit.create({
                userId: req.user.id,
                resignationId: resignation.id,
                action: 'PARTIAL_APPROVE',
                remarks: `Partial approval by ${req.user.roleCode}${remarks ? ': ' + remarks : ''}`,
                details: { roleCode: req.user.roleCode, stage: sourceStage }
            }, { transaction });

            // Ensure worknote is added for this partial approval
            if (remarks) {
                await writeWorkflowActivityWorknote({
                    requestId: resignation.id,
                    requestType: 'resignation',
                    userId: req.user.id,
                    noteText: `Approved: ${remarks}`,
                    noteType: 'internal'
                });
            }

            // Check if both RBM and DD_ZM have approved
            const requiredRoles = [ROLES.RBM, ROLES.DD_ZM];
            const partialLogs = await db.ResignationAudit.findAll({
                where: {
                    resignationId: resignation.id,
                    action: 'PARTIAL_APPROVE'
                },
                transaction
            });

            const approvedRoles = new Set(
                partialLogs.map((log: any) => log.details?.roleCode)
            );

            const hasAllRequiredApprovals = requiredRoles.every(role => approvedRoles.has(role));

            if (!hasAllRequiredApprovals) {
                // Append to timeline directly without transitioning the stage
                const timelineEntry = {
                    stage: sourceStage,
                    targetStage: nextStage,
                    timestamp: new Date(),
                    user: req.user.fullName,
                    action: `Approved by ${req.user.roleCode}`,
                    remarks: remarks || ''
                };
                const updatedTimeline = [...(resignation.timeline || []), timelineEntry];
                await resignation.update({ timeline: updatedTimeline }, { transaction });

                await transaction.commit();
                return res.json({
                    success: true,
                    message: 'Approval recorded. Waiting for the other required approver (RBM or DD-ZM).',
                    resignation
                });
            }
        }

        // Transition via Workflow Service
        await ResignationWorkflowService.transitionResignation(resignation, nextStage, req.user.id, {
            remarks,
            actionType: OFFBOARDING_ACTIONS.APPROVE,
            status: getResignationStatusForStage(nextStage),
            transaction
        });

        // Special logic for F&F and Completion
        if (nextStage === RESIGNATION_STAGES.COMPLETED) {
            await (resignation as any).outlet.update({ status: 'Closed' }, { transaction });
            ExternalMocksService.mockSyncDealerStatusToSap((resignation as any).outlet.code, 'Inactive')
                .catch(err => logger.error('Error syncing resignation completion to SAP:', err));
        }

        if (shouldTriggerFnF) {
            const existingFnF = await db.FnF.findOne({ where: { resignationId: resignation.id }, transaction });
            if (!existingFnF) {
                const fnf = await ResignationWorkflowService.initiateFnF(resignation, req.user.id, transaction);
                // Assign/sync Participants for F&F (Sub-application chat) to ensure robustness
                await ParticipantService.assignFnFParticipants(fnf.id);
            }
        }

        await transaction.commit();

        let message = 'Resignation approved successfully';
        if (nextStage === RESIGNATION_STAGES.AWAITING_FNF) {
            message =
                'DD Admin approval recorded. Use Push to F&F when ready to create the Full & Final settlement (Last Working Day rules apply).';
            const lwdForSchedule =
                resignation.lastOperationalDateServices || resignation.lastOperationalDateSales;
            if (lwdForSchedule) {
                const { OffboardingLwdReminderService } = await import('../../services/OffboardingLwdReminderService.js');
                await OffboardingLwdReminderService.scheduleDelayedReminderIfNeeded(
                    { requestType: 'resignation', requestId: resignation.id },
                    lwdForSchedule
                );
            }
        } else if (sourceStage === RESIGNATION_STAGES.LEGAL && nextStage === RESIGNATION_STAGES.DD_ADMIN) {
            message =
                'Legal stage approved successfully. After DD Admin review, use Push to F&F to start settlement when ready.';
        }

        res.json({ success: true, message, nextStage, resignation });
    } catch (error) {
        if (transaction) await transaction.rollback();
        logger.error('Error approving resignation:', error);
        next(error);
    }
};

// Reject resignation
export const rejectResignation = async (req: AuthRequest, res: Response, next: NextFunction) => {
    const transaction: Transaction = await db.sequelize.transaction();
    try {
        if (!req.user) throw new Error('Unauthorized');
        const { id } = req.params;
        const { reason } = req.body;
        if (!reason) {
            await transaction.rollback();
            return res.status(400).json({ success: false, message: 'Rejection reason is required' });
        }

        const resolvedId = await resolveResignationUuid(String(id));
        const resignation = await db.Resignation.findOne({
            where: { id: resolvedId },
            include: [{ model: db.Outlet, as: 'outlet' }]
        });
        if (!resignation) {
            await transaction.rollback();
            return res.status(404).json({ success: false, message: 'Resignation not found' });
        }

        await ResignationWorkflowService.transitionResignation(resignation, RESIGNATION_STAGES.REJECTED, req.user.id, {
            remarks: reason,
            action: OFFBOARDING_ACTIONS.REJECT,
            actionType: OFFBOARDING_ACTIONS.REJECT,
            status: 'Rejected'
        });

        await (resignation as any).outlet.update({ status: 'Active' }, { transaction });
        await transaction.commit();

        res.json({ success: true, message: 'Resignation rejected', resignation });
    } catch (error) {
        if (transaction) await transaction.rollback();
        logger.error('Error rejecting resignation:', error);
        next(error);
    }
};

// Withdraw resignation
export const withdrawResignation = async (req: AuthRequest, res: Response, next: NextFunction) => {
    const transaction: Transaction = await db.sequelize.transaction();
    try {
        if (!req.user) throw new Error('Unauthorized');
        const { id } = req.params;
        const { reason } = req.body;
        const resolvedId = await resolveResignationUuid(String(id));

        const resignation = await db.Resignation.findOne({
            where: { id: resolvedId },
            include: [{ model: db.Outlet, as: 'outlet' }]
        });
        if (!resignation) {
            await transaction.rollback();
            return res.status(404).json({ success: false, message: 'Resignation not found' });
        }

        const restrictedStages = [
            RESIGNATION_STAGES.NBH,
            RESIGNATION_STAGES.DD_ADMIN,
            RESIGNATION_STAGES.AWAITING_FNF,
            RESIGNATION_STAGES.LEGAL,
            RESIGNATION_STAGES.FNF_INITIATED,
            RESIGNATION_STAGES.COMPLETED
        ];

        if (restrictedStages.includes(resignation.currentStage as any)) {
            await transaction.rollback();
            return res.status(400).json({
                success: false,
                message: `Withdrawal not allowed after NBH evaluation stage. Current stage: ${resignation.currentStage}`
            });
        }

        await ResignationWorkflowService.transitionResignation(resignation, RESIGNATION_STAGES.REJECTED, req.user.id, {
            remarks: reason,
            action: 'Withdrawn',
            status: 'Withdrawn'
        });

        await (resignation as any).outlet.update({ status: 'Active' }, { transaction });
        await transaction.commit();

        res.json({ success: true, message: 'Resignation withdrawn successfully' });
    } catch (error) {
        if (transaction) await transaction.rollback();
        logger.error('Error withdrawing resignation:', error);
        next(error);
    }
};

// Send back resignation
export const sendBackResignation = async (req: AuthRequest, res: Response, next: NextFunction) => {
    const transaction: Transaction = await db.sequelize.transaction();
    try {
        if (!req.user) throw new Error('Unauthorized');
        const { id } = req.params;
        const { targetStage, remarks } = req.body;
        const resolvedId = await resolveResignationUuid(String(id));

        const resignation = await db.Resignation.findOne({
            where: { id: resolvedId }
        });
        if (!resignation) {
            await transaction.rollback();
            return res.status(404).json({ success: false, message: 'Resignation not found' });
        }

        // Standardized validation
        const validation = validateOffboardingAction(OFFBOARDING_ACTIONS.SEND_BACK, remarks);
        if (!validation.valid) {
            await transaction.rollback();
            return res.status(400).json({ success: false, message: validation.message });
        }

        const prevStage = targetStage || getPreviousStage(REQUEST_TYPES.RESIGNATION, resignation.currentStage);
        if (!prevStage) {
            await transaction.rollback();
            return res.status(400).json({ success: false, message: 'Cannot send back from current stage' });
        }

        await ResignationWorkflowService.transitionResignation(resignation, prevStage, req.user.id, {
            remarks,
            action: OFFBOARDING_ACTIONS.SEND_BACK,
            status: `${getResignationStatusForStage(prevStage)} (Sent Back)`
        });

        await transaction.commit();
        res.json({ success: true, message: `Resignation sent back to ${prevStage}` });
    } catch (error) {
        if (transaction) await transaction.rollback();
        logger.error('Error sending back resignation:', error);
        next(error);
    }
};

// Revoke resignation (Standardized Action)
export const revokeResignation = async (req: AuthRequest, res: Response, next: NextFunction) => {
    const transaction: Transaction = await db.sequelize.transaction();
    try {
        if (!req.user) throw new Error('Unauthorized');
        const { id } = req.params;
        const { remarks } = req.body;
        const resolvedId = await resolveResignationUuid(String(id));

        const resignation = await db.Resignation.findOne({
            where: { id: resolvedId }
        });
        if (!resignation) {
            await transaction.rollback();
            return res.status(404).json({ success: false, message: 'Resignation not found' });
        }

        // Standardized validation
        const validation = validateOffboardingAction(OFFBOARDING_ACTIONS.REVOKE, remarks);
        if (!validation.valid) {
            await transaction.rollback();
            return res.status(400).json({ success: false, message: validation.message });
        }

        // Transition to REJECTED stage with Revoked status (Terminal)
        await ResignationWorkflowService.transitionResignation(resignation, RESIGNATION_STAGES.REJECTED, req.user.id, {
            remarks,
            action: OFFBOARDING_ACTIONS.REVOKE,
            status: 'Revoked'
        });

        await transaction.commit();
        res.json({ success: true, message: `Resignation for ${resignation.resignationId} has been revoked and closed.` });
    } catch (error) {
        if (transaction) await transaction.rollback();
        logger.error('Error revoking resignation:', error);
        next(error);
    }
};

// Update departmental clearance (existing code)...

// Manually assign participant
export const assignResignation = async (req: AuthRequest, res: Response, next: NextFunction) => {
    try {
        if (!req.user) throw new Error('Unauthorized');
        const { id } = req.params;
        const { assignTo, remarks } = req.body; // assignTo is a role code or specific userId

        const resolvedId = await resolveResignationUuid(String(id));
        const resignation = await db.Resignation.findOne({
            where: { id: resolvedId },
            include: [{ model: db.User, as: 'dealer' }]
        });

        if (!resignation) {
            return res.status(404).json({ success: false, message: 'Resignation not found' });
        }

        let targetUserId = null;

        // If assignTo is a UUID, it's a direct user assignment
        const isUUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(assignTo);

        if (isUUID) {
            targetUserId = assignTo;
        } else {
            // Role-based resolution
            const user = await db.User.findByPk(resignation.dealerId);
            if (user && user.dealerId) {
                const dealer = await db.Dealer.findByPk(user.dealerId, {
                    include: [{
                        model: db.Application,
                        as: 'application',
                        include: [{
                            model: db.District,
                            as: 'district',
                            include: [{ model: db.Region, as: 'region' }, { model: db.Zone, as: 'zone' }]
                        }]
                    }]
                });

                if (dealer?.application?.district) {
                    const d = dealer.application.district;
                    if (assignTo === 'asm') targetUserId = dealer.asmId || null;
                    else if (assignTo === 'rbm') targetUserId = d.region?.rbmId;
                    else if (assignTo === 'zbh') targetUserId = d.zone?.zbhId;
                }
            }

            // Fallback for national roles
            if (!targetUserId) {
                const roleIdMap: Record<string, string> = {
                    'nbh': ROLES.NBH,
                    'legal': ROLES.LEGAL_ADMIN,
                    'dd_admin': ROLES.DD_ADMIN,
                    'dd_lead': ROLES.DD_LEAD
                };
                const targetRole = roleIdMap[assignTo];
                if (targetRole) {
                    const roleUser = await db.User.findOne({ where: { roleCode: targetRole, status: 'active' } });
                    if (roleUser) targetUserId = roleUser.id;
                }
            }
        }

        if (!targetUserId) {
            return res.status(400).json({
                success: false,
                message: `Could not resolve a unique user for assignment: ${assignTo}. Please ensure the underlying master data (District/Region/Zone) is correctly mapped.`
            });
        }

        await db.RequestParticipant.findOrCreate({
            where: {
                requestId: resignation.id,
                requestType: REQUEST_TYPES.RESIGNATION,
                userId: targetUserId
            },
            defaults: {
                participantType: 'contributor',
                joinedMethod: 'manual',
                metadata: {
                    assignedBy: req.user.id,
                    remarks: remarks || 'Manual assignment'
                }
            }
        });

        await db.ResignationAudit.create({
            userId: req.user.id,
            resignationId: resignation.id,
            action: AUDIT_ACTIONS.UPDATED,
            remarks: `Manually assigned user to the request. ${remarks || ''}`,
            details: { assignedUserId: targetUserId, roleToAssign: assignTo }
        });

        res.json({ success: true, message: 'Participant assigned successfully' });
    } catch (error) {
        logger.error('Error assigning resignation:', error);
        next(error);
    }
};
export const updateClearance = async (req: AuthRequest, res: Response, next: NextFunction) => {
    const transaction: Transaction = await db.sequelize.transaction();
    try {
        if (!req.user) throw new Error('Unauthorized');
        const { id } = req.params;
        const { department, status, remarks, amount, type } = req.body;

        // Align with F&F: dealer-owed side is always stored as Receivable (legacy payloads may send Recovery)
        const clearanceType = String(type || '').toLowerCase();
        const resolvedItemType: 'Payable' | 'Receivable' | 'Deduction' =
            clearanceType === 'payable'
                ? 'Payable'
                : clearanceType === 'deduction'
                    ? 'Deduction'
                    : clearanceType === 'recovery' || clearanceType === 'receivable'
                        ? 'Receivable'
                        : type === 'Payable' || type === 'Deduction'
                            ? type
                            : type === 'Recovery'
                                ? 'Receivable'
                                : type === 'Receivable'
                                    ? 'Receivable'
                                    : 'Receivable';

        const clearanceStoredType: 'Payable' | 'Receivable' | 'Deduction' =
            resolvedItemType === 'Payable'
                ? 'Payable'
                : resolvedItemType === 'Deduction'
                    ? 'Deduction'
                    : 'Receivable';
        const resolvedId = await resolveResignationUuid(String(id));

        const resignation = await db.Resignation.findOne({
            where: { id: resolvedId }
        });
        if (!resignation) {
            await transaction.rollback();
            return res.status(404).json({ success: false, message: 'Resignation not found' });
        }

        const currentClearances = resignation.departmentalClearances || {};
        const normalizedAmount = Math.abs(parseFloat(amount) || 0);
        const normalizedDeptStatus = normalizeClearanceStatus(status, normalizedAmount);
        const documentUrl = req.file ? `/uploads/documents/${req.file.filename}` : (currentClearances[department]?.supportingDocument || null);

        const clearances = {
            ...currentClearances,
            [department]: {
                status: normalizedDeptStatus,
                remarks,
                amount: normalizedAmount,
                type: clearanceStoredType,
                supportingDocument: documentUrl,
                updatedAt: new Date().toISOString(),
                updatedBy: req.user.fullName
            }
        };

        await resignation.update({
            departmentalClearances: clearances,
            timeline: [...resignation.timeline, {
                stage: resignation.currentStage,
                timestamp: new Date(),
                user: req.user.fullName,
                action: `Updated clearance for ${department}: ${normalizedDeptStatus}`,
                remarks
            }]
        }, { transaction });

        // Record module-specific audit
        await db.ResignationAudit.create({
            userId: req.user.id,
            resignationId: resignation.id,
            action: 'CLEARANCE_UPDATED',
            remarks: remarks || `Cleared ${department}`,
            details: { department, status: normalizedDeptStatus, amount: normalizedAmount }
        }, { transaction });

        // Sync with F&F Clearance if settlement exists
        const fnf = await db.FnF.findOne({ where: { resignationId: resignation.id } });
        if (fnf) {
            const numAmount = normalizedAmount;
            const fnfStatus = normalizeClearanceStatus(status, numAmount);

            const existingClearance = await db.FffClearance.findOne({
                where: { fnfId: fnf.id, department },
                transaction
            });

            if (existingClearance) {
                await existingClearance.update({
                    status: fnfStatus,
                    remarks: remarks || '-',
                    clearedAt: new Date(),
                    supportingDocument: documentUrl
                }, { transaction });
            } else {
                await db.FffClearance.create({
                    fnfId: fnf.id,
                    department,
                    status: fnfStatus,
                    remarks: remarks || '-',
                    clearedAt: new Date(),
                    supportingDocument: documentUrl
                }, { transaction });
            }

            // Record F&F specific audit
            await db.FnFAudit.create({
                userId: req.user.id,
                fnfId: fnf.id,
                action: 'CLEARANCE_UPDATED',
                remarks: remarks || `Departmental clearance recorded for ${department}`,
                details: { department, status: fnfStatus, source: 'Resignation Workflow' }
            }, { transaction });

            // Write department claim in versioned, active-only model.
            const enteredAmount = Math.abs(parseFloat(amount) || 0);
            const existingClaim = await db.FnFLineItem.findOne({
                where: {
                    fnfId: fnf.id,
                    department,
                    sourceType: 'DepartmentClaim',
                    isActive: true
                },
                transaction
            });

            if (enteredAmount > 0) {
                if (existingClaim) {
                    await existingClaim.update({ isActive: false }, { transaction });
                }
                await db.FnFLineItem.create({
                    fnfId: fnf.id,
                    itemType: resolvedItemType,
                    description: '[DEPARTMENT_CLAIM] Department Clearance - Manual Update',
                    department,
                    amount: enteredAmount,
                    addedBy: req.user.id,
                    sourceType: 'DepartmentClaim',
                    version: Number(existingClaim?.version || 1) + (existingClaim ? 1 : 0),
                    isActive: true,
                    parentLineItemId: existingClaim?.parentLineItemId || existingClaim?.id || null,
                    claimAmount: enteredAmount,
                    validatedAmount: null,
                    varianceAmount: 0,
                    financeDecision: null,
                    varianceReason: null
                }, { transaction });
            } else if (existingClaim) {
                await existingClaim.update({ isActive: false }, { transaction });
            }

            // Recalculate totals from active lines only.
            // If finance-validated rows exist, use only those rows for totals.
            const items = await db.FnFLineItem.findAll({ where: { fnfId: fnf.id, isActive: true }, transaction });
            const hasFinanceValidated = items.some((item: any) => item.sourceType === 'FinanceValidated');
            const calculationItems = hasFinanceValidated
                ? items.filter((item: any) => item.sourceType === 'FinanceValidated')
                : items;
            let totalPayables = 0;
            let totalReceivables = 0;
            let totalDeductions = 0;

            calculationItems.forEach((item: any) => {
                const val = Math.abs(parseFloat(item.amount) || 0);
                if (item.itemType === 'Payable') totalPayables += val;
                else if (item.itemType === 'Receivable' || item.itemType === 'Recovery') totalReceivables += val;
                else if (item.itemType === 'Deduction') totalDeductions += val;
            });

            await fnf.update({
                totalPayables,
                totalReceivables,
                totalDeductions,
                netAmount: totalPayables - totalReceivables - totalDeductions
            }, { transaction });
        }

        await transaction.commit();

        if (fnf) {
            const { syncFnfClearanceSla } = await import('../../common/utils/slaFnfSync.js');
            await syncFnfClearanceSla(fnf.id, department, normalizedDeptStatus);
        }

        res.json({ success: true, message: `Clearance updated for ${department}`, resignation });
    } catch (error) {
        if (transaction) await transaction.rollback();
        logger.error('Error updating clearance:', error);
        next(error);
    }
};
// Unified status update handler for frontend compatibility
export const updateResignationStatus = async (req: AuthRequest, res: Response, next: NextFunction) => {
    try {
        const { action } = req.body;

        // Normalize to lowercase alphanumeric for robust comparison (handles "Send Back", "sendBack", "send-back")
        const actionNode = String(action || '').toLowerCase().trim().replace(/[^a-z0-9]/g, '');

        switch (actionNode) {
            case 'approve':
                return approveResignation(req, res, next);
            case 'reject':
                return rejectResignation(req, res, next);
            case 'revoke':
                return revokeResignation(req, res, next);
            case 'withdrawal':
            case 'withdraw':
                return withdrawResignation(req, res, next);
            case 'sendback':
                return sendBackResignation(req, res, next);
            case 'pushfnf':
                // Verify if user role is authorized for manual jump to F&F
                const authorizedRoles = [ROLES.DD_LEAD, ROLES.DD_HEAD, ROLES.DD_ADMIN, ROLES.SUPER_ADMIN];
                if (!req.user || !authorizedRoles.includes(req.user.roleCode as any)) {
                    return res.status(403).json({ success: false, message: 'You do not have permission to push this request to F&F' });
                }
                {
                    const resolvedId = await resolveResignationUuid(String(req.params.id));
                    const resignation = await db.Resignation.findByPk(resolvedId);
                    if (!resignation) {
                        return res.status(404).json({ success: false, message: 'Resignation not found' });
                    }

                    // SRS-aligned gate: F&F can start only after Legal completion artifacts.
                    const pushAllowedStages = [
                        RESIGNATION_STAGES.AWAITING_FNF,
                        // Legacy rows: acceptance letter uploaded at Legal before DD Admin step completed in older builds
                        RESIGNATION_STAGES.LEGAL
                    ];
                    if (!pushAllowedStages.includes(resignation.currentStage as any)) {
                        return res.status(400).json({
                            success: false,
                            message: `Cannot trigger F&F from ${resignation.currentStage}. Complete DD Admin review first (stage must be Awaiting F&F), or use Legal only for legacy cases.`
                        });
                    }

                    const hasLegalStageDocument = await db.ResignationDocument.findOne({
                        where: {
                            resignationId: resignation.id,
                            stage: RESIGNATION_STAGES.LEGAL,
                            documentType: 'Resignation Acceptance Letter'
                        },
                        attributes: ['id']
                    });
                    if (!hasLegalStageDocument) {
                        return res.status(400).json({
                            success: false,
                            message: 'Cannot trigger F&F. Resignation Acceptance Letter is required first.'
                        });
                    }
                }
                // Jump directly to F&F Initiation
                (req as any).targetStage = RESIGNATION_STAGES.FNF_INITIATED;
                return approveResignation(req, res, next);

            case 'assign':
                return assignResignation(req, res, next);

            case 'dispatch':
                return dispatchResignationLetter(req, res, next);

            default:
                return res.status(400).json({
                    success: false,
                    message: `Invalid or unsupported resignation action: ${action}`
                });
        }
    } catch (error) {
        logger.error('Error in updateResignationStatus:', error);
        next(error);
    }
};

/**
 * Dispatch the Resignation Acceptance Letter to the dealer (DD Admin step).
 *
 * Workflow context:
 *   Legal Admin uploads the Resignation Acceptance Letter at the Legal stage and
 *   approves → the request transitions to DD Admin. At DD Admin, the assigned admin
 *   formally sends the dispatched copy of the letter to the dealer (email +
 *   download link). On successful dispatch the request advances to Awaiting F&F.
 *
 * Authorization:
 *   - DD Admin (assigned role for the DD Admin stage)
 *   - Super Admin (bypass)
 */
export const dispatchResignationLetter = async (req: AuthRequest, res: Response, next: NextFunction) => {
    const transaction: Transaction = await db.sequelize.transaction();
    try {
        if (!req.user) throw new Error('Unauthorized');

        const allowedRoles: string[] = [ROLES.DD_ADMIN, ROLES.SUPER_ADMIN];
        if (!allowedRoles.includes(req.user.roleCode as any)) {
            await transaction.rollback();
            return res.status(403).json({
                success: false,
                message: 'Only DD Admin or Super Admin can dispatch the resignation acceptance letter.'
            });
        }

        const { id } = req.params;
        const remarks: string = (req.body?.remarks || '').trim();
        const resolvedId = await resolveResignationUuid(String(id));

        const resignation = await db.Resignation.findOne({
            where: { id: resolvedId },
            include: [{ model: db.Outlet, as: 'outlet' }],
            transaction
        });
        if (!resignation) {
            await transaction.rollback();
            return res.status(404).json({ success: false, message: 'Resignation not found' });
        }

        // Dispatch is available from DD Admin onwards. If the admin forgot to
        // send the letter at DD Admin, the action remains accessible while the
        // request moves through Awaiting F&F / F&F Initiated, so the dealer can
        // still be informed retroactively. Once the case is closed (Completed /
        // Rejected / Withdrawn / Revoked) dispatch is no longer meaningful.
        const dispatchAllowedStages: string[] = [
            RESIGNATION_STAGES.DD_ADMIN,
            RESIGNATION_STAGES.AWAITING_FNF,
            RESIGNATION_STAGES.FNF_INITIATED
        ];
        const closedStatuses = ['Completed', 'Rejected', 'Withdrawn', 'Revoked', 'Settled'];
        if (
            !dispatchAllowedStages.includes(resignation.currentStage as any) ||
            closedStatuses.includes(String(resignation.status || ''))
        ) {
            await transaction.rollback();
            return res.status(400).json({
                success: false,
                message: `Dispatch can be performed only from DD Admin stage onwards while the request is still open. Current stage: ${resignation.currentStage}, status: ${resignation.status}.`
            });
        }

        // Block duplicate dispatch — once the letter has been sent we don't
        // want a second email going out without the user explicitly knowing.
        const existingDispatch = await db.ResignationAudit.findOne({
            where: {
                resignationId: resignation.id,
                action: AUDIT_ACTIONS.RESIGNATION_LETTER_DISPATCHED
            },
            transaction
        });
        if (existingDispatch) {
            await transaction.rollback();
            return res.status(400).json({
                success: false,
                message: 'Resignation acceptance letter has already been dispatched to the dealer.'
            });
        }

        // Pick the most recent acceptance letter — preferentially typed as
        // "Resignation Acceptance Letter"; fall back to any document uploaded at
        // the Legal stage (covers legacy uploads where documentType was generic).
        const acceptanceLetter = await db.ResignationDocument.findOne({
            where: {
                resignationId: resignation.id,
                [Op.or]: [
                    { documentType: 'Resignation Acceptance Letter' },
                    { stage: RESIGNATION_STAGES.LEGAL }
                ]
            },
            order: [['createdAt', 'DESC']],
            transaction
        });

        if (!acceptanceLetter) {
            await transaction.rollback();
            return res.status(400).json({
                success: false,
                message: 'No Resignation Acceptance Letter is available. Ask Legal to upload the letter before dispatching.'
            });
        }

        // Lookup dealer user for the dealer email channel.
        // `Resignation.dealerId` is used inconsistently across the codebase —
        // it may point at the dealer-User directly (User.id) for internal /
        // legacy rows, or at the Dealer record (Dealer.id) which is then
        // referenced from User.dealerId. Mirror the resolution used by
        // ResignationWorkflowService.transitionResignation so we cover both.
        const dealerUser = await db.User.findOne({
            where: {
                [Op.or]: [
                    { id: resignation.dealerId },
                    { dealerId: resignation.dealerId }
                ]
            },
            transaction
        });

        const portalBase = getFrontendBaseUrl();
        const dealerName = dealerUser?.fullName || (resignation as any).outlet?.name || 'Dealer';
        const resignationCode = resignation.resignationId || resignation.id;
        const lwd = resignation.lastOperationalDateServices || resignation.lastOperationalDateSales || 'N/A';
        const portalLink = `${portalBase}/dealer-resignation/${resignation.id}`;

        // Resolve the acceptance letter on disk so we can attach the file itself
        // to the email instead of redirecting the dealer to our portal.
        // `acceptanceLetter.filePath` is stored as `/uploads/documents/<name>`;
        // map it back to UPLOAD_DIR using just the basename so we never let an
        // injected path escape the documents folder.
        const uploadDir = process.env.UPLOAD_DIR || './uploads';
        const documentsDir = path.resolve(uploadDir, 'documents');
        const letterDiskPath = path.resolve(documentsDir, path.basename(acceptanceLetter.filePath));
        const letterFileExists = fs.existsSync(letterDiskPath);
        if (!letterFileExists) {
            logger.warn(
                `[dispatchResignationLetter] Acceptance letter file missing on disk: ${letterDiskPath}. Email will be sent without attachment.`
            );
        }

        // Record audit + timeline entry before sending notifications so the trail
        // is consistent even if the email transport blips. The transition below
        // is the workflow gate; this row provides a verbose human-readable note.
        await db.ResignationAudit.create({
            userId: req.user.id,
            resignationId: resignation.id,
            action: AUDIT_ACTIONS.RESIGNATION_LETTER_DISPATCHED,
            remarks: remarks || `Resignation acceptance letter dispatched to ${dealerName}`,
            details: {
                fileName: acceptanceLetter.fileName,
                documentId: acceptanceLetter.id,
                dealerEmail: dealerUser?.email || null,
                stage: RESIGNATION_STAGES.DD_ADMIN
            }
        }, { transaction });

        const timeline = [...(resignation.timeline || []), {
            stage: RESIGNATION_STAGES.DD_ADMIN,
            timestamp: new Date(),
            user: req.user.fullName,
            role: req.user.roleCode,
            action: 'Resignation Letter Dispatched',
            remarks: remarks || `Acceptance letter sent to ${dealerName}${dealerUser?.email ? ` (${dealerUser.email})` : ''}`
        }];
        await resignation.update({ timeline }, { transaction });

        const wasAtDDAdmin = resignation.currentStage === RESIGNATION_STAGES.DD_ADMIN;

        await transaction.commit();

        // Advance the workflow only when the dispatch happens at the DD Admin step.
        // If the admin missed dispatch and the case has already moved on
        // (Awaiting F&F / F&F Initiated), we send the letter without touching the
        // current stage to avoid an artificial "step back".
        if (wasAtDDAdmin) {
            await ResignationWorkflowService.transitionResignation(
                resignation,
                RESIGNATION_STAGES.AWAITING_FNF,
                req.user.id,
                {
                    action: 'Resignation Letter Dispatched',
                    remarks: remarks || 'Acceptance letter dispatched to dealer.'
                }
            );
        }

        // Send the formal dispatch email to the dealer with the acceptance
        // letter file attached. Non-fatal: log and proceed if the mailer hiccups.
        if (dealerUser?.email) {
            try {
                await sendEmail(
                    dealerUser.email,
                    `Resignation Acceptance Letter Dispatched: ${resignationCode}`,
                    'RESIGNATION_LETTER_DISPATCHED_DEALER',
                    {
                        dealerName,
                        resignationId: String(resignationCode),
                        lwd: String(lwd),
                        dispatchedBy: req.user.fullName || 'Dealer Development Office',
                        link: portalLink,
                        ctaLabel: 'View Resignation Status'
                    },
                    letterFileExists
                        ? {
                              attachments: [
                                  {
                                      filename: acceptanceLetter.fileName,
                                      path: letterDiskPath,
                                      contentType: acceptanceLetter.mimeType || undefined
                                  }
                              ]
                          }
                        : undefined
                );
            } catch (mailErr) {
                logger.error('[dispatchResignationLetter] dealer email failed:', mailErr);
            }
        } else {
            logger.warn(
                `[dispatchResignationLetter] No dealer user/email found for resignation ${resignation.resignationId}; in-app/portal notification only.`
            );
        }

        return res.json({
            success: true,
            message: wasAtDDAdmin
                ? 'Resignation acceptance letter dispatched to the dealer. Request moved to Awaiting F&F.'
                : 'Resignation acceptance letter dispatched to the dealer. Workflow stage left unchanged.',
            nextStage: wasAtDDAdmin ? RESIGNATION_STAGES.AWAITING_FNF : resignation.currentStage,
            document: {
                id: acceptanceLetter.id,
                fileName: acceptanceLetter.fileName,
                filePath: acceptanceLetter.filePath
            }
        });
    } catch (error) {
        if (transaction) {
            try { await transaction.rollback(); } catch (_) { /* already finalized */ }
        }
        logger.error('Error dispatching resignation letter:', error);
        next(error);
    }
};
