import { Response, NextFunction } from 'express';
import { Op } from 'sequelize';
import db from '../../database/models/index.js';
import logger from '../../common/utils/logger.js';
import {
    TERMINATION_STAGES,
    AUDIT_ACTIONS,
    ROLES,
    TERMINATION_DOCUMENT_TYPES,
    TERMINATION_DOCUMENT_STAGES
} from '../../common/config/constants.js';
import { Transaction } from 'sequelize';
import { AuthRequest } from '../../types/express.types.js';
import ExternalMocksService from '../../common/utils/externalMocks.service.js';

import { TerminationWorkflowService } from '../../services/TerminationWorkflowService.js';
import { NomenclatureService } from '../../common/utils/nomenclature.js';
import { ParticipantService } from '../../services/ParticipantService.js';
import { getTerminationStatusForStage, normalizeClearanceStatus, getLegacyTerminationRowFixes } from '../../common/utils/offboardingStatus.js';
import {
    buildJointRoundCreatedAtFilter,
    getJointRoundCutoffMsFromTimeline
} from '../../common/utils/terminationJointReviewRound.util.js';
import { resolveEntityUuidByType } from '../../common/utils/requestResolver.js';
import { OFFBOARDING_ACTIONS, REQUEST_TYPES } from '../../common/config/constants.js';
import { validateOffboardingAction, getPreviousStage } from '../../common/utils/offboardingWorkflow.utils.js';
import { NotificationService } from '../../services/NotificationService.js';
import { sendEmail } from '../../common/utils/email.service.js';
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';

const resolveTerminationUuid = async (id: string) => {
    const { resolvedId } = await resolveEntityUuidByType(db as any, id, 'termination');
    return resolvedId;
};

// Create termination request
export const createTermination = async (req: AuthRequest, res: Response, next: NextFunction) => {
    const transaction: Transaction = await db.sequelize.transaction();
    try {
        if (!req.user) throw new Error('Unauthorized');

        const allowedRoles = [ROLES.DD_LEAD, ROLES.ASM, ROLES.DD_ADMIN, ROLES.DD_AM, ROLES.SUPER_ADMIN];
        if (!allowedRoles.includes(req.user.roleCode as any)) {
            return res.status(403).json({ 
                success: false, 
                message: 'Only DD Lead, ASM, DD Admin, or DD AM are authorized to initiate termination requests.' 
            });
        }

        const uploadedFiles: Express.Multer.File[] = Array.isArray(req.files)
            ? (req.files as Express.Multer.File[])
            : [];

        const PPT_MIME_TYPES = [
            'application/vnd.ms-powerpoint',
            'application/vnd.openxmlformats-officedocument.presentationml.presentation'
        ];
        const isPptFile = (file: Express.Multer.File) => {
            const lowerName = String(file.originalname || '').toLowerCase();
            const hasPptExt = lowerName.endsWith('.ppt') || lowerName.endsWith('.pptx');
            const hasPptMime = PPT_MIME_TYPES.includes(file.mimetype);
            return hasPptMime || hasPptExt;
        };

        const isPresentationMandatory = req.user.roleCode !== ROLES.SUPER_ADMIN;
        if (isPresentationMandatory) {
            if (uploadedFiles.length === 0) {
                await transaction.rollback();
                return res.status(400).json({
                    success: false,
                    message: 'At least one Presentation (.ppt or .pptx) is mandatory to initiate a termination request.'
                });
            }
            const hasAtLeastOnePpt = uploadedFiles.some(isPptFile);
            if (!hasAtLeastOnePpt) {
                await transaction.rollback();
                return res.status(400).json({
                    success: false,
                    message: 'At least one PowerPoint file (.ppt or .pptx) is required as the Presentation.'
                });
            }
        }

        const { dealerId, category, reason, proposedLwd, comments } = req.body;

        const requestId = await NomenclatureService.generateTerminationId();
        const isUnethical = String(category).trim().toLowerCase().includes('unethical');
        const startStage = isUnethical ? TERMINATION_STAGES.DD_LEAD_REVIEW : TERMINATION_STAGES.RBM_REVIEW;
        
        const termination = await db.TerminationRequest.create({
            requestId,
            dealerId,
            category,
            reason,
            proposedLwd,
            comments,
            initiatedBy: req.user.id,
            currentStage: startStage,
            status: getTerminationStatusForStage(startStage),
            progressPercentage: TerminationWorkflowService.calculateProgress(startStage),
            timeline: [{
                stage: 'Submitted',
                targetStage: startStage,
                timestamp: new Date(),
                user: req.user.fullName,
                action: isUnethical ? 'Immediate escalation due to Unethical Practice' : `Termination request initiated and forwarded to ${startStage}`,
                remarks: comments
            }]
        }, { transaction });

        await db.TerminationAudit.create({
            userId: req.user.id,
            action: AUDIT_ACTIONS.CREATED,
            terminationRequestId: termination.id,
            remarks: 'Admin initiated termination request'
        }, { transaction });

        if (uploadedFiles.length > 0) {
            const stage = 'Submitted';
            for (const file of uploadedFiles) {
                const documentType = isPptFile(file) ? 'Presentation' : 'Other';
                const filePath = `/uploads/documents/${file.filename}`;

                await db.TerminationDocument.create({
                    terminationRequestId: termination.id,
                    documentType,
                    fileName: file.originalname,
                    filePath,
                    fileSize: file.size,
                    mimeType: file.mimetype,
                    stage,
                    uploadedBy: req.user.id
                }, { transaction });

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

        await transaction.commit();

        // Add as chat participants (Async)
        ParticipantService.assignTerminationParticipants(termination.id)
            .catch(err => logger.error('Error assigning participants to termination:', err));

        // SRS §4.3.2.1 — Notify appropriate stakeholders that a new termination has been initiated
        const notifyOnCreateRoles = isUnethical ? [ROLES.DD_LEAD] : [ROLES.RBM, ROLES.DD_ZM];
        for (const role of notifyOnCreateRoles) {
            const roleUsers = await db.User.findAll({ where: { roleCode: role } });
            for (const u of roleUsers) {
                const phone = (u as any).mobileNumber || null;
                NotificationService.notify(u.id, u.email, {
                    title: `New Termination Request: ${termination.requestId}`,
                    message: `A termination request has been initiated by ${req.user!.fullName || 'Admin'}. Your review is required.`,
                    channels: phone ? ['system', 'email', 'whatsapp'] : ['system', 'email'],
                    templateCode: 'TERMINATION_INITIATED',
                    placeholders: {
                        dealerName: '',
                        requestId: termination.requestId,
                        reason: reason || '',
                        link: `${getFrontendBaseUrl()}/termination/${termination.id}`,
                        ctaLabel: 'Review Request',
                        phone: phone || ''
                    }
                }).catch((e: any) => logger.error('[Termination] Create notify failed:', e));
            }
        }

        res.status(201).json({ success: true, message: 'Termination request created', termination });
    } catch (error) {
        if (transaction) await transaction.rollback();
        logger.error('Error creating termination:', error);
        next(error);
    }
};

// Get all termination requests
export const getTerminations = async (req: AuthRequest, res: Response, next: NextFunction) => {
    try {
        if (!req.user) throw new Error('Unauthorized');
        const { dealerId } = req.query;
        const where: any = {};
        
        if (dealerId) where.dealerId = dealerId;
        if (req.user.roleCode === ROLES.DEALER) {
            const dealer = await db.Dealer.findOne({ where: { userId: req.user.id } });
            if (dealer) where.dealerId = dealer.id;
        } else {
            const { status } = req.query;
            if (status) {
                if (status === 'open') {
                    where.status = { [Op.notIn]: ['Terminated', 'Completed', 'Closed', 'Rejected'] };
                } else if (status === 'completed') {
                    where.status = { [Op.in]: ['Terminated', 'Completed', 'Closed'] };
                } else {
                    where.status = status;
                }
            }
        }

        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: terminations } = await db.TerminationRequest.findAndCountAll({
            where,
            include: [
                { 
                    model: db.Dealer, 
                    as: 'dealer',
                    include: [{ model: db.DealerCode, as: 'dealerCode' }]
                }
            ],
            order: [['createdAt', 'DESC']],
            limit,
            offset,
            distinct: true
        });
        res.json({ 
            success: true, 
            terminations,
            meta: {
                total: count,
                totalPages: Math.ceil(count / limit),
                currentPage: page,
                limit,
                stats: {
                    total: count,
                    open: await db.TerminationRequest.count({ where: { ...where, status: { [Op.notIn]: ['Terminated', 'Completed', 'Closed', 'Rejected'] } } }),
                    completed: await db.TerminationRequest.count({ where: { ...where, status: { [Op.in]: ['Terminated', 'Completed', 'Closed'] } } })
                }
            }
        });
    } catch (error) {
        logger.error('Error fetching terminations:', error);
        next(error);
    }
};

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

        const termination = await db.TerminationRequest.findOne({
            where: { id: resolvedId },
            include: [
                { 
                    model: db.Dealer, 
                    as: 'dealer',
                    include: [
                        { 
                            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.DealerCode, as: 'dealerCode' },
                        { model: db.User, as: 'user', attributes: ['id', 'email', 'mobileNumber', 'status'] }
                    ]
                },
                { model: db.User, as: 'initiator', attributes: ['id', 'fullName', 'email'] },
                { 
                    model: db.TerminationDocument, 
                    as: 'uploadedDocuments',
                    include: [{ model: db.User, as: 'uploader', attributes: ['fullName'] }]
                },
                { model: db.FnF, as: 'fnfSettlement' },
                { 
                    model: db.RequestParticipant, 
                    as: 'participants',
                    include: [{ model: db.User, as: 'user', attributes: ['id', 'fullName', 'email', 'roleCode'] }]
                }
            ]
        });

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

        const legacyTerminationFixes = getLegacyTerminationRowFixes(termination as any);
        if (legacyTerminationFixes) {
            await termination.update(legacyTerminationFixes);
            (termination as any).setDataValue('currentStage', legacyTerminationFixes.currentStage ?? termination.currentStage);
            (termination as any).setDataValue('status', legacyTerminationFixes.status ?? termination.status);
        }

        res.json({ success: true, termination });
    } catch (error) {
        logger.error('Error fetching termination:', error);
        next(error);
    }
};

export const uploadTerminationDocument = 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 { documentType = TERMINATION_DOCUMENT_TYPES[0], stage = null } = req.body;

        if (!req.file) {
            await transaction.rollback();
            return res.status(400).json({ success: false, message: 'File is required' });
        }

        if (!TERMINATION_DOCUMENT_TYPES.includes(documentType)) {
            await transaction.rollback();
            return res.status(400).json({
                success: false,
                message: `Invalid document type. Allowed values: ${TERMINATION_DOCUMENT_TYPES.join(', ')}`
            });
        }
        if (stage && !TERMINATION_DOCUMENT_STAGES.includes(stage)) {
            await transaction.rollback();
            return res.status(400).json({
                success: false,
                message: `Invalid stage. Allowed values: ${TERMINATION_DOCUMENT_STAGES.join(', ')}`
            });
        }
        const resolvedId = await resolveTerminationUuid(String(id));
        const termination = await db.TerminationRequest.findOne({
            where: { id: resolvedId }
        });

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

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

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

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

        const normalizedStage = String(stage || '').trim().toLowerCase();
        const isScnStageUpload = normalizedStage === 'show cause notice' || normalizedStage === 'show cause notice (scn)' || normalizedStage === 'scn';
        const isScnResponseDoc = String(documentType || '').trim().toLowerCase() === 'scn response';
        if (
            termination.currentStage === TERMINATION_STAGES.SCN_ISSUED &&
            (isScnStageUpload || isScnResponseDoc)
        ) {
            await TerminationWorkflowService.handleScnResponse(termination, {
                responseBody: `SCN response uploaded: ${req.file.originalname}`,
                documents: [{ fileName: req.file.originalname, filePath }]
            }, req.user.id);
        }

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

// Update termination status (Approve/Reject)
export const updateTerminationStatus = 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 { action, remarks } = req.body;
        const resolvedId = await resolveTerminationUuid(String(id));

        const termination = await db.TerminationRequest.findByPk(resolvedId);
        if (!termination) {
            await transaction.rollback();
            return res.status(404).json({ success: false, message: 'Termination not found' });
        }

        const legacyTerminationFixes = getLegacyTerminationRowFixes(termination as any);
        if (legacyTerminationFixes) {
            await termination.update(legacyTerminationFixes, { transaction });
            if (legacyTerminationFixes.currentStage) {
                (termination as any).setDataValue('currentStage', legacyTerminationFixes.currentStage);
            }
            if (legacyTerminationFixes.status) {
                (termination as any).setDataValue('status', legacyTerminationFixes.status);
            }
        }

        // Workflow-changing actions are gated by role. Only the role assigned to the
        // current stage can act; Super Admin is the sole role that can bypass any stage.
        // DD Admin is administrative and intentionally has no approve/reject power.
        const workflowChangingActions: string[] = [
            OFFBOARDING_ACTIONS.REJECT,
            OFFBOARDING_ACTIONS.REVOKE,
            OFFBOARDING_ACTIONS.SEND_BACK,
            'sendback',
            'approve'
        ];
        const isWorkflowChanging =
            workflowChangingActions.includes(action) || (action !== 'pushfnf' && action !== OFFBOARDING_ACTIONS.HOLD);
        if (isWorkflowChanging) {
            const isSuperAdmin = req.user.roleCode === ROLES.SUPER_ADMIN;
            const allowed = isSuperAdmin
                ? true
                : await TerminationWorkflowService.canUserAction(termination, req.user);
            if (!allowed) {
                await transaction.rollback();
                return res.status(403).json({
                    success: false,
                    message: 'You are not authorized to approve, reject, send back, or revoke this termination request at the current stage. Only the stage owner or a Super Admin may take this action.'
                });
            }
        }

        const fromStage = termination.currentStage;
        let approvedToStage: string | null = null;
        let scheduleLwdFnfReminder = false;

        if (action === OFFBOARDING_ACTIONS.REJECT) {
            await TerminationWorkflowService.transitionTermination(termination, TERMINATION_STAGES.REJECTED, req.user.id, {
                action: 'Rejected',
                status: 'Rejected',
                remarks
            });
        } else if (action === OFFBOARDING_ACTIONS.HOLD) {
            // SRS §4.3.2.7 — Hold Decision (Pause temporarily); NBH may hold at evaluation or final approval
            const holdStages = [TERMINATION_STAGES.NBH_EVALUATION, TERMINATION_STAGES.NBH_FINAL_APPROVAL];
            if (!holdStages.includes(termination.currentStage as any) && req.user.roleCode !== ROLES.SUPER_ADMIN) {
                await transaction.rollback();
                return res.status(400).json({
                    success: false,
                    message: 'Hold action is only available at NBH Evaluation or NBH Final Approval stage.'
                });
            }
            await termination.update({ status: 'On Hold' }, { transaction });
            await db.TerminationAudit.create({
                userId: req.user.id,
                terminationRequestId: termination.id,
                action: 'ON_HOLD',
                remarks: remarks || 'Case placed on hold for further monitoring.',
                details: { stage: fromStage }
            }, { transaction });
            
            await transaction.commit();

            const { SLAService } = await import('../../services/SLAService.js');
            await SLAService.pauseEntityTracks('termination', termination.id);

            return res.json({ success: true, message: 'Termination case placed on hold.' });
        } else if (action === OFFBOARDING_ACTIONS.REVOKE) {
            // Validation: Remarks mandatory for Revoke
            const validation = validateOffboardingAction(action, remarks);
            if (!validation.valid) {
                await transaction.rollback();
                return res.status(400).json({ success: false, message: validation.message });
            }

            await TerminationWorkflowService.transitionTermination(termination, TERMINATION_STAGES.REJECTED, req.user.id, {
                action: 'Revoked',
                status: 'Revoked',
                remarks
            });
        } else if (action === OFFBOARDING_ACTIONS.SEND_BACK || action === 'sendback') {
            // Validation: Remarks mandatory for Send Back
            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 previousStage = getPreviousStage(REQUEST_TYPES.TERMINATION, termination.currentStage);
            if (!previousStage) {
                await transaction.rollback();
                return res.status(400).json({ success: false, message: 'Cannot send back from current stage' });
            }

            await TerminationWorkflowService.transitionTermination(termination, previousStage, req.user.id, {
                action: 'Sent Back',
                remarks
            });
        } else if (action === 'pushfnf') {
            // FnF row is created ONLY here — never on stage approval or LWD scheduler.
            const awaitingFnfStatuses = ['Awaiting F&F', 'Awaiting F&F (LWD Pending)', 'Terminated'];
            const atTerminatedStage = termination.currentStage === TERMINATION_STAGES.TERMINATED;
            const awaitingFnfStatus = awaitingFnfStatuses.includes(String(termination.status || ''));

            if (!atTerminatedStage && !awaitingFnfStatus) {
                await transaction.rollback();
                return res.status(400).json({
                    success: false,
                    message: `Cannot trigger F&F from ${termination.currentStage}. Complete Legal approval so the case reaches Terminated / Awaiting F&F first.`
                });
            }

            const existingFnf = await db.FnF.findOne({
                where: { terminationRequestId: termination.id },
                transaction
            });
            if (existingFnf) {
                await transaction.rollback();
                return res.status(400).json({
                    success: false,
                    message: 'F&F settlement already exists for this termination. Open the F&F module to continue clearance.'
                });
            }

            const { force } = req.body;
            const { isLwdReached, formatLwdDisplay } = await import('../../common/utils/offboardingLwd.js');
            if (!force && !isLwdReached(termination.proposedLwd)) {
                await transaction.rollback();
                return res.status(400).json({
                    success: false,
                    message: `F&F settlement can be initiated only on or after the Last Working Day (${formatLwdDisplay(termination.proposedLwd)}).`,
                    canForce: true
                });
            }

            const forceInitiated = Boolean(force);
            logger.info(
                `[TerminationController] Manual Push to F&F for Termination ${termination.requestId} (force=${forceInitiated})`
            );
            await TerminationWorkflowService.initiateFnF(termination, req.user.id, transaction, {
                manualTrigger: true
            });

            // Maintain timeline visibility — "Forced" only when LWD was bypassed
            const timeline = [...(termination.timeline || []), {
                stage: termination.currentStage,
                timestamp: new Date(),
                user: req.user.fullName,
                role: req.user.roleCode,
                action: forceInitiated ? 'Forced F&F Initiation' : 'F&F Initiated',
                remarks:
                    remarks ||
                    (forceInitiated
                        ? 'F&F settlement initiated before Last Working Day (exception / force).'
                        : 'F&F settlement initiated via Push to F&F after Last Working Day.')
            }];
            await termination.update({ 
                currentStage: TERMINATION_STAGES.TERMINATED, // Lock out further approvals
                status: 'F&F Initiated', 
                timeline 
            }, { transaction });
        } else {
            const stageFlow: Record<string, string> = {
                [TERMINATION_STAGES.SUBMITTED]: TERMINATION_STAGES.RBM_REVIEW,
                [TERMINATION_STAGES.RBM_REVIEW]: TERMINATION_STAGES.ZBH_REVIEW,
                [TERMINATION_STAGES.ZBH_REVIEW]: TERMINATION_STAGES.DD_LEAD_REVIEW,
                [TERMINATION_STAGES.DD_LEAD_REVIEW]: TERMINATION_STAGES.LEGAL_VERIFICATION,
                [TERMINATION_STAGES.LEGAL_VERIFICATION]: TERMINATION_STAGES.DD_HEAD_REVIEW,
                [TERMINATION_STAGES.DD_HEAD_REVIEW]: TERMINATION_STAGES.NBH_EVALUATION,
                [TERMINATION_STAGES.NBH_EVALUATION]: TERMINATION_STAGES.SCN_ISSUED,
                [TERMINATION_STAGES.SCN_ISSUED]: TERMINATION_STAGES.PERSONAL_HEARING,
                [TERMINATION_STAGES.PERSONAL_HEARING]: TERMINATION_STAGES.NBH_FINAL_APPROVAL,
                [TERMINATION_STAGES.NBH_FINAL_APPROVAL]: TERMINATION_STAGES.CCO_APPROVAL,
                [TERMINATION_STAGES.CCO_APPROVAL]: TERMINATION_STAGES.CEO_APPROVAL,
                [TERMINATION_STAGES.CEO_APPROVAL]: TERMINATION_STAGES.LEGAL_LETTER,
                [TERMINATION_STAGES.LEGAL_LETTER]: TERMINATION_STAGES.TERMINATED
            };

            const sourceStage = termination.currentStage;
            const nextStage = stageFlow[sourceStage];
            logger.info(`[TerminationController] attempting transition from ${sourceStage} to ${nextStage}`);

            if (!nextStage) {
                await transaction.rollback();
                return res.status(400).json({ success: false, message: 'Cannot approve from current stage' });
            }

            // SRS §4.3.2.2 — JOINT APPROVAL LOGIC FOR RBM STAGE
            if (sourceStage === TERMINATION_STAGES.RBM_REVIEW && req.user.roleCode !== ROLES.SUPER_ADMIN) {
                const rbmRoundTime = buildJointRoundCreatedAtFilter(
                    getJointRoundCutoffMsFromTimeline(termination.timeline, 'rbm_review')
                );
                // Prevent duplicate approval from same user
                const existingUserApproval = await db.TerminationAudit.findOne({
                    where: {
                        terminationRequestId: termination.id,
                        userId: req.user.id,
                        action: 'PARTIAL_APPROVE',
                        'details.stage': sourceStage,
                        ...rbmRoundTime
                    },
                    transaction
                });

                if (existingUserApproval) {
                    await transaction.rollback();
                    return res.status(400).json({ success: false, message: 'You have already recorded your approval for this stage.' });
                }

                // 1. Record this partial approval in Audit Logs
                await db.TerminationAudit.create({
                    userId: req.user.id,
                    terminationRequestId: termination.id,
                    action: 'PARTIAL_APPROVE',
                    remarks: `Partial approval by ${req.user.roleCode}${remarks ? ': ' + remarks : ''}`,
                    details: { roleCode: req.user.roleCode, stage: sourceStage }
                }, { transaction });


                // 2. Check for both RBM and DD_ZM approvals in this stage
                const requiredRoles = [ROLES.RBM, ROLES.DD_ZM];
                const partialLogs = await db.TerminationAudit.findAll({
                    where: {
                        terminationRequestId: termination.id,
                        action: 'PARTIAL_APPROVE',
                        'details.stage': sourceStage,
                        ...rbmRoundTime
                    },
                    transaction
                });

                const approvedRoles = partialLogs.map((log: { details?: { roleCode?: string } }) => log.details?.roleCode);
                const isComplete = requiredRoles.every(role => approvedRoles.includes(role));

                if (!isComplete) {
                    // Record partial approval in timeline ONLY if not complete yet
                    // (The final approver's entry will be handled by transitionTermination)
                    const partialTimeline = [...(termination.timeline || []), {
                        stage: sourceStage,
                        timestamp: new Date(),
                        user: req.user.fullName,
                        role: req.user.roleCode,
                        action: 'Partial Approved',
                        remarks: remarks || `Partial approval recorded by ${req.user.roleCode}`
                    }];
                    await termination.update({ timeline: partialTimeline }, { transaction });

                    await transaction.commit();
                    return res.json({ 
                        success: true, 
                        message: `Partial approval recorded. Waiting for ${requiredRoles.find(r => !approvedRoles.includes(r))} approval to proceed to ZBH Review.`, 
                        isPartial: true 
                    });
                }
                
                logger.info(`[TerminationController] Joint approval complete for ${termination.requestId}. Moving to ${nextStage}.`);
            }

            // SRS §4.3.2.9 — JOINT APPROVAL LOGIC FOR SCN EVALUATION (PERSONAL HEARING STAGE)
            if (sourceStage === TERMINATION_STAGES.PERSONAL_HEARING && req.user.roleCode !== ROLES.SUPER_ADMIN) {
                const scnEvalAuditStages = [TERMINATION_STAGES.PERSONAL_HEARING, 'Personal Hearing'];
                const scnRoundTime = buildJointRoundCreatedAtFilter(
                    getJointRoundCutoffMsFromTimeline(termination.timeline, 'scn_response_eval')
                );
                const existingUserApproval = await db.TerminationAudit.findOne({
                    where: {
                        terminationRequestId: termination.id,
                        userId: req.user.id,
                        action: 'PARTIAL_APPROVE',
                        [Op.or]: scnEvalAuditStages.map((s) => ({ 'details.stage': s })),
                        ...scnRoundTime
                    },
                    transaction
                });

                if (existingUserApproval) {
                    await transaction.rollback();
                    return res.status(400).json({ success: false, message: 'You have already recorded your approval for this stage.' });
                }

                await db.TerminationAudit.create({
                    userId: req.user.id,
                    terminationRequestId: termination.id,
                    action: 'PARTIAL_APPROVE',
                    remarks: `SCN Response Review by ${req.user.roleCode}${remarks ? ': ' + remarks : ''}`,
                    details: { roleCode: req.user.roleCode, stage: sourceStage }
                }, { transaction });

                const requiredRoles = [ROLES.DD_LEAD, ROLES.ZBH, ROLES.RBM, ROLES.DD_HEAD];
                const partialLogs = await db.TerminationAudit.findAll({
                    where: {
                        terminationRequestId: termination.id,
                        action: 'PARTIAL_APPROVE',
                        [Op.or]: scnEvalAuditStages.map((s) => ({ 'details.stage': s })),
                        ...scnRoundTime
                    },
                    transaction
                });

                const approvedRoles = partialLogs.map((log: { details?: { roleCode?: string } }) => log.details?.roleCode);
                const isComplete = requiredRoles.every(role => approvedRoles.includes(role));

                if (!isComplete) {
                    const partialTimeline = [...(termination.timeline || []), {
                        stage: sourceStage,
                        timestamp: new Date(),
                        user: req.user.fullName,
                        role: req.user.roleCode,
                        action: 'Partial Approved (SCN Review)',
                        remarks: remarks || `Review recorded by ${req.user.roleCode}`
                    }];
                    await termination.update({ timeline: partialTimeline }, { transaction });

                    await transaction.commit();
                    return res.json({ 
                        success: true, 
                        message: `Review recorded. Waiting for ${requiredRoles.filter(r => !approvedRoles.includes(r)).join(', ')} approval to proceed to NBH Final Approval.`, 
                        isPartial: true 
                    });
                }
                logger.info(`[TerminationController] SCN Joint evaluation complete for ${termination.requestId}. Moving to ${nextStage}.`);
            }

            approvedToStage = nextStage;

            await TerminationWorkflowService.transitionTermination(termination, nextStage, req.user.id, {
                remarks: remarks || `Jointly approved by RBM & DD-ZM`,
                status: getTerminationStatusForStage(nextStage),
                transaction
            });

            // F&F is never started automatically on termination; authorized users run Push to F&F when ready.
            if (nextStage === TERMINATION_STAGES.TERMINATED) {
                const today = new Date();
                const lwd = new Date(termination.proposedLwd);
                today.setHours(0, 0, 0, 0);
                lwd.setHours(0, 0, 0, 0);
                const statusAfterTerm =
                    today < lwd ? 'Awaiting F&F (LWD Pending)' : 'Awaiting F&F';
                await termination.update({ status: statusAfterTerm }, { transaction });
                logger.info(
                    `[TerminationController] Termination reached TERMINATED. F&F must be started manually (Push to F&F). LWD=${termination.proposedLwd}, status=${statusAfterTerm}`
                );
                scheduleLwdFnfReminder = true;
            }
        }

        await transaction.commit();

        if (scheduleLwdFnfReminder) {
            const { OffboardingLwdReminderService } = await import('../../services/OffboardingLwdReminderService.js');
            await OffboardingLwdReminderService.scheduleDelayedReminderIfNeeded(
                { requestType: 'termination', requestId: termination.id },
                termination.proposedLwd
            );
        }

        res.json({ success: true, message: 'Termination updated', termination });
    } catch (error) {
        if (transaction) await transaction.rollback();
        logger.error('Error updating termination:', error);
        next(error);
    }
};



// Submit SCN Response (Dealer Principal)
export const submitScnResponse = async (req: AuthRequest, res: Response, next: NextFunction) => {
    const transaction: Transaction = await db.sequelize.transaction();
    try {
        if (!req.user) throw new Error('Unauthorized');

        const authorizedRoles = [ROLES.DD_ADMIN, ROLES.DD_LEAD, ROLES.LEGAL_ADMIN, ROLES.SUPER_ADMIN];
        if (!authorizedRoles.includes(req.user.roleCode as any)) {
            return res.status(403).json({ success: false, message: 'Direct SCN submission is restricted. Please submit your response to DD Admin.' });
        }
        const { terminationRequestId, responseBody, documents } = req.body;

        const termination = await db.TerminationRequest.findByPk(terminationRequestId);
        if (!termination) throw new Error('Termination request not found');

        const response = await TerminationWorkflowService.handleScnResponse(termination, { responseBody, documents }, req.user.id);

        await transaction.commit();
        res.status(201).json({ success: true, message: 'SCN Response submitted successfully', response });
    } catch (error) {
        if (transaction) await transaction.rollback();
        logger.error('Error submitting SCN response:', error);
        next(error);
    }
};

// Issue SCN for a specific termination request id (frontend-compatible route)
export const issueScn = 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 resolveTerminationUuid(String(id));

        const termination = await db.TerminationRequest.findByPk(resolvedId);
        if (!termination) {
            await transaction.rollback();
            return res.status(404).json({ success: false, message: 'Termination request not found' });
        }

        if (termination.currentStage === TERMINATION_STAGES.NBH_EVALUATION) {
            await TerminationWorkflowService.transitionTermination(termination, TERMINATION_STAGES.SCN_ISSUED, req.user.id, {
                action: 'SCN Issued',
                status: 'Show Cause Notice',
                remarks: remarks || 'Show Cause Notice issued'
            });

            // SRS §4.3.2.8 — SCN issued: send official email to dealer (no WhatsApp)
            const dealer = await db.Dealer.findByPk(termination.dealerId, {
                include: [{ model: db.User, as: 'user', attributes: ['email', 'mobileNumber', 'fullName'] }]
            });
            const dealerUser = (dealer as any)?.user;
            if (dealerUser?.email) {
                sendEmail(
                    dealerUser.email,
                    `Show Cause Notice: ${termination.requestId}`,
                    'TERMINATION_SCN_ISSUED',
                    {
                        dealerName: dealerUser.fullName || 'Dealer',
                        requestId: termination.requestId,
                        link: `${getFrontendBaseUrl()}/dealer-termination/${termination.id}`,
                        ctaLabel: 'View Notice'
                    }
                ).catch((e: any) => logger.error('[Termination] SCN email to dealer failed:', e));
            }

            // Notify DD-Admin + Legal of SCN issuance
            const scnAlertRoles = [ROLES.DD_ADMIN, ROLES.LEGAL_ADMIN];
            for (const role of scnAlertRoles) {
                const roleUsers = await db.User.findAll({ where: { roleCode: role } });
                for (const u of roleUsers) {
                    NotificationService.notify(u.id, u.email, {
                        title: `SCN Issued: ${termination.requestId}`,
                        message: `Show Cause Notice has been issued for termination case ${termination.requestId}.`,
                        channels: ['system', 'email'],
                        templateCode: 'TERMINATION_SCN_ISSUED',
                        placeholders: {
                            dealerName: dealerUser?.fullName || '',
                            requestId: termination.requestId,
                            link: `${getFrontendBaseUrl()}/termination/${termination.id}`,
                            ctaLabel: 'View Case'
                        }
                    }).catch((e: any) => logger.error('[Termination] SCN admin/legal notify failed:', e));
                }
            }
        }

        await transaction.commit();
        return res.json({ success: true, message: 'SCN issued successfully', termination });
    } catch (error) {
        if (transaction) await transaction.rollback();
        logger.error('Error issuing SCN:', error);
        next(error);
    }
};

// Upload SCN response by route param id (frontend-compatible route)
export const uploadScnResponse = async (req: AuthRequest, res: Response, next: NextFunction) => {
    const transaction: Transaction = await db.sequelize.transaction();
    try {
        if (!req.user) throw new Error('Unauthorized');

        const authorizedRoles = [ROLES.DD_ADMIN, ROLES.DD_LEAD, ROLES.LEGAL_ADMIN, ROLES.SUPER_ADMIN];
        if (!authorizedRoles.includes(req.user.roleCode as any)) {
            return res.status(403).json({ success: false, message: 'Only DD Admin or DD Lead can upload the dealer SCN response.' });
        }
        const { id } = req.params;
        const { remarks } = req.body;
        const resolvedId = await resolveTerminationUuid(String(id));

        const termination = await db.TerminationRequest.findByPk(resolvedId);
        if (!termination) {
            await transaction.rollback();
            return res.status(404).json({ success: false, message: 'Termination request not found' });
        }

        if (req.file) {
            const filePath = `/uploads/documents/${req.file.filename}`;
            await db.TerminationDocument.create({
                terminationRequestId: termination.id,
                documentType: 'SCN Response',
                fileName: req.file.originalname,
                filePath,
                fileSize: req.file.size,
                mimeType: req.file.mimetype,
                stage: TERMINATION_STAGES.SCN_ISSUED,
                uploadedBy: req.user.id
            }, { transaction });
        }

        // Move SCN -> Personal Hearing after response submission.
        if (termination.currentStage === TERMINATION_STAGES.SCN_ISSUED) {
            await TerminationWorkflowService.handleScnResponse(termination, {
                responseBody: remarks || 'SCN response uploaded via portal',
                documents: req.file ? [{ fileName: req.file.originalname }] : []
            }, req.user.id);
        }

        await transaction.commit();
        return res.status(201).json({ success: true, message: 'SCN response uploaded successfully', termination });
    } catch (error) {
        if (transaction) await transaction.rollback();
        logger.error('Error uploading SCN response:', error);
        next(error);
    }
};

// Record Personal Hearing Outcome
export const recordPersonalHearing = async (req: AuthRequest, res: Response, next: NextFunction) => {
    const transaction: Transaction = await db.sequelize.transaction();
    try {
        if (!req.user) throw new Error('Unauthorized');
        const { terminationRequestId, attendees, summary, recommendation, momDocumentId } = req.body;

        const termination = await db.TerminationRequest.findByPk(terminationRequestId);
        if (!termination) throw new Error('Termination request not found');

        const hearing = await TerminationWorkflowService.handleHearingOutcome(termination, { attendees, summary, recommendation, momDocumentId }, req.user.id);

        await transaction.commit();
        res.status(201).json({ success: true, message: 'Hearing record saved', recommendation });
    } catch (error) {
        if (transaction) await transaction.rollback();
        logger.error('Error recording hearing:', error);
        next(error);
    }
};

// Final Authorization (NBH Final / CCO / CEO)
export const finalizeTermination = 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 { decision, remarks } = req.body as { decision?: 'Approve' | 'Reject' | 'Reconsider'; remarks?: string };
        const resolvedId = await resolveTerminationUuid(String(id));

        const termination = await db.TerminationRequest.findByPk(resolvedId);
        if (!termination) {
            await transaction.rollback();
            return res.status(404).json({ success: false, message: 'Termination request not found' });
        }

        const currentStage = termination.currentStage;
        const allowedFinalizeStages = [
            TERMINATION_STAGES.NBH_FINAL_APPROVAL,
            TERMINATION_STAGES.CCO_APPROVAL,
            TERMINATION_STAGES.CEO_APPROVAL
        ];
        if (!allowedFinalizeStages.includes(currentStage as any)) {
            await transaction.rollback();
            return res.status(400).json({
                success: false,
                message: `Finalize action is not allowed at stage: ${currentStage}`
            });
        }

        if (decision === 'Reject') {
            await TerminationWorkflowService.transitionTermination(termination, TERMINATION_STAGES.REJECTED, req.user.id, {
                action: 'Final Authorization Rejected',
                actionType: OFFBOARDING_ACTIONS.REJECT,
                status: 'Rejected',
                remarks: remarks || 'Rejected during final authorization'
            });
        } else if (decision === 'Reconsider' || decision === 'Send Back' as any) {
            // Standardizing Send Back / Reconsideration logic
            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 previousStage = getPreviousStage(REQUEST_TYPES.TERMINATION, termination.currentStage);
            const targetStage = previousStage || TERMINATION_STAGES.NBH_EVALUATION; // Fallback to NBH if manual resolve fails

            await TerminationWorkflowService.transitionTermination(termination, targetStage, req.user.id, {
                action: 'Sent for Reconsideration',
                actionType: OFFBOARDING_ACTIONS.RECONSIDER,
                status: getTerminationStatusForStage(targetStage),
                remarks: remarks || 'Sent back for reconsideration'
            });
        } else {
            const approveFlow: Record<string, string> = {
                [TERMINATION_STAGES.NBH_FINAL_APPROVAL]: TERMINATION_STAGES.CCO_APPROVAL,
                [TERMINATION_STAGES.CCO_APPROVAL]: TERMINATION_STAGES.CEO_APPROVAL,
                [TERMINATION_STAGES.CEO_APPROVAL]: TERMINATION_STAGES.LEGAL_LETTER
            };
            const targetStage = approveFlow[currentStage];
            await TerminationWorkflowService.transitionTermination(termination, targetStage, req.user.id, {
                action: `Final Authorization Approved to ${targetStage}`,
                actionType: OFFBOARDING_ACTIONS.APPROVE,
                status: getTerminationStatusForStage(targetStage),
                remarks: remarks || 'Approved'
            });
        }

        await transaction.commit();
        return res.json({ success: true, message: 'Final authorization processed', termination });
    } catch (error) {
        if (transaction) await transaction.rollback();
        logger.error('Error finalizing termination:', error);
        next(error);
    }
};

// Record Clearance from Departments (16-Department F&F)
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, amount, type, remarks } = req.body;
        const resolvedId = await resolveTerminationUuid(String(id));

        const termination = await db.TerminationRequest.findByPk(resolvedId);
        if (!termination) throw new Error('Termination request not found');

        const clearances = { ...(termination.departmentalClearances || {}) };
        const normalizedStatus = normalizeClearanceStatus(status, Number(amount) || 0);
        clearances[department] = {
            status: normalizedStatus,
            amount: Number(amount) || 0,
            type: type || 'Receivable',
            remarks: remarks || '',
            updatedAt: new Date().toISOString(),
            updatedBy: req.user.fullName
        };

        const fnf = await db.FnF.findOne({ where: { terminationRequestId: resolvedId }, transaction });
        if (!fnf) {
            await transaction.rollback();
            return res.status(400).json({
                success: false,
                message:
                    'F&F settlement has not been started for this termination. Use Push to F&F on the termination case first.'
            });
        }

        await termination.update({ departmentalClearances: clearances }, { transaction });

        // Update individual clearance record for unified dashboard
        if (fnf) {
            await db.FffClearance.update(
                { status: normalizedStatus, remarks, amount: Number(amount) || 0 },
                { where: { fnfId: fnf.id, department }, transaction }
            );
        }

        await db.TerminationAudit.create({
            userId: req.user.id,
            action: 'CLEARANCE_UPDATED',
            terminationRequestId: resolvedId,
            remarks: remarks || `Cleared ${department}`,
            details: { department, status: normalizedStatus, amount }
        }, { transaction });

        if (fnf) {
            await db.FnFAudit.create({
                userId: req.user.id,
                fnfId: fnf.id,
                action: 'CLEARANCE_UPDATED',
                remarks: remarks || `Departmental clearance recorded for ${department}`,
                details: { department, status: normalizedStatus, source: 'Termination Workflow' }
            }, { transaction });
        }

        await transaction.commit();

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

        res.json({ success: true, message: `Clearance updated for ${department}`, clearances });
    } catch (error) {
        if (transaction) await transaction.rollback();
        logger.error('Error updating termination clearance:', error);
        next(error);
    }
};

