import { Request, Response } from 'express';
import db from '../../database/models/index.js';
const {
    Questionnaire, QuestionnaireQuestion, QuestionnaireResponse, QuestionnaireScore,
    Interview, InterviewEvaluation, InterviewParticipant, AiSummary, User, RequestParticipant, Role, District, StageApprovalPolicy, StageApprovalAction
} = db;
import { AuthRequest } from '../../types/express.types.js';
import { Op } from 'sequelize';
import * as EmailService from '../../common/utils/email.service.js';
import { APPLICATION_STAGES, APPLICATION_STATUS, AUDIT_ACTIONS } from '../../common/config/constants.js';
import { WorkflowService } from '../../services/WorkflowService.js';
import { syncApplicationProgress, PIPELINE_STAGE_LABEL_BY_OVERALL_STATUS } from '../../common/utils/progress.js';
import { shouldTrackOnboardingSla } from '../../common/config/slaStageCatalog.js';
import { SLAService } from '../../services/SLAService.js';
import { NotificationService } from '../../services/NotificationService.js';

const getLocationAncestors = async (locationId: string): Promise<string[]> => {
    const district: any = await District.findByPk(locationId);
    if (!district) return [locationId];
    return [district.id, district.stateId, district.regionId, district.zoneId].filter(Boolean);
};

const interviewStageCode = (level: number) => `INTERVIEW_LEVEL_${level}`;

const formatIST = (date: Date) => {
    return new Intl.DateTimeFormat('en-IN', {
        timeZone: 'Asia/Kolkata',
        day: '2-digit',
        month: 'short',
        year: 'numeric',
        hour: '2-digit',
        minute: '2-digit',
        hour12: true
    }).format(date);
};

const normalizeRoleCode = (value: unknown) =>
    String(value || '')
        .trim()
        .toLowerCase()
        .replace(/[_\s-]+/g, ' ');

const getDefaultInterviewPolicy = (level: number) => {
    const defaults: Record<number, { requiredRoles: string[]; minApprovals: number }> = {
        1: { requiredRoles: ['DD-ZM', 'RBM'], minApprovals: 2 },
        2: { requiredRoles: ['ZBH', 'DD Lead'], minApprovals: 2 },
        3: { requiredRoles: ['NBH', 'DD Head'], minApprovals: 2 }
    };
    return defaults[level] || { requiredRoles: [], minApprovals: 1 };
};

const ensureInterviewPolicy = async (level: number) => {
    const stageCode = interviewStageCode(level);
    const defaultPolicy = getDefaultInterviewPolicy(level);
    const [policy] = await StageApprovalPolicy.findOrCreate({
        where: { stageCode },
        defaults: {
            stageCode,
            minApprovals: defaultPolicy.minApprovals,
            approvalMode: 'ROLE_MANDATORY',
            requiredRoles: defaultPolicy.requiredRoles,
            isActive: true
        }
    });
    return policy;
};

const getRequiredRolesForInterviewLevel = (level: number): string[] => {
    const defaults = getDefaultInterviewPolicy(level);
    return Array.isArray(defaults.requiredRoles) ? defaults.requiredRoles : [];
};

const processStageDecision = async (params: {
    applicationId: string;
    stageCode: string;
    decision: 'Approved' | 'Rejected';
    remarks?: string;
    userId: string;
    roleCode: string;
    interviewId?: string;
    nextStatus?: string;
    nextStage?: string;
    nextProgress?: number;
}) => {
    const { applicationId, stageCode, decision, remarks, userId, roleCode, interviewId, nextStatus, nextStage, nextProgress } = params;
    
    const targetId = applicationId as string;
    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(targetId);

    const application = await db.Application.findOne({
        where: isUUID ? { [Op.or]: [{ id: targetId }, { applicationId: targetId }] } : { applicationId: targetId }
    });

    if (!application) return { notFound: true };
    const resolvedId = application.id;

    const policy = await db.StageApprovalPolicy.findOne({ where: { stageCode } });
    if (!policy) return { noPolicy: true };

    const requiredRoles: string[] = Array.isArray(policy.requiredRoles) ? policy.requiredRoles : [];

    // Strict interview gating: only designated evaluators for this interview can submit decision.
    if (interviewId && roleCode !== 'Super Admin') {
        const normalizedRequiredRoles = requiredRoles.map(normalizeRoleCode);
        const normalizedActorRole = normalizeRoleCode(roleCode);
        const evaluatorParticipant = await InterviewParticipant.findOne({
            where: {
                interviewId,
                userId,
                roleInPanel: 'Evaluator'
            }
        });
        const panelParticipant = await InterviewParticipant.findOne({
            where: {
                interviewId,
                userId
            }
        });
        const interviewRow: any = await Interview.findByPk(interviewId, { attributes: ['applicationId', 'level'] });
        const mappedApprover = interviewRow
            ? await RequestParticipant.findOne({
                where: {
                    requestId: interviewRow.applicationId,
                    requestType: 'application',
                    userId
                }
            })
            : null;
        const mappedAsStageApprover = Boolean(
            mappedApprover && (
                mappedApprover.metadata?.interviewLevel === interviewRow?.level ||
                mappedApprover.metadata?.interviewLevel === String(interviewRow?.level) ||
                mappedApprover.metadata?.allAssignments?.includes(interviewRow?.level) ||
                mappedApprover.metadata?.allAssignments?.includes(String(interviewRow?.level))
            )
        );
        if (!evaluatorParticipant) {
            const roleEligiblePanelist = Boolean(
                panelParticipant && normalizedRequiredRoles.includes(normalizedActorRole)
            );
            if (mappedAsStageApprover || roleEligiblePanelist) {
                // Auto-heal historic/scheduling mismatch: elevate mapped stage approver to evaluator.
                await InterviewParticipant.update(
                    { roleInPanel: 'Evaluator' },
                    { where: { interviewId, userId } }
                );
            } else {
            return {
                forbidden: true,
                policy,
                requiredRoles,
                currentRole: roleCode,
                message: 'Only designated stage evaluators can submit interview feedback/decision. Additional panelists are view participants.'
            };
            }
        }
    }

    // Check if user is an assigned participant
    const userAssignments = await db.RequestParticipant.findAll({
        where: { requestId: resolvedId, requestType: 'application', userId }
    });

    const isAssigned = userAssignments.some((p: any) => {
        if (!p.metadata) return false;
        if (interviewId && p.metadata.interviewLevel) return true; 
        if (p.metadata.stageCode === stageCode) return true;
        if (Array.isArray(p.metadata.allAssignments) && p.metadata.allAssignments.includes(stageCode)) return true;
        return false;
    });

    const assignedRole = userAssignments.find((p: any) => p.metadata?.role)?.metadata?.role;
    
    if (roleCode !== 'Super Admin' && requiredRoles.length > 0 && !requiredRoles.includes(roleCode) && !isAssigned) {
        return { forbidden: true, policy, requiredRoles, currentRole: roleCode };
    }

    // RECORD THE DECISION ACTION
    // Record Action - Robust handle for null interviewId which breaks unique constraint in Postgres
    if (!interviewId) {
        const existing = await db.StageApprovalAction.findOne({
            where: { applicationId: resolvedId, stageCode, actorUserId: userId, interviewId: null }
        });
        if (existing) {
            await existing.update({ decision, remarks: remarks || null, actorRole: assignedRole || roleCode });
        } else {
            await db.StageApprovalAction.create({
                applicationId: resolvedId,
                stageCode,
                actorUserId: userId,
                actorRole: assignedRole || roleCode,
                decision,
                remarks: remarks || null
            });
        }
    } else {
        await db.StageApprovalAction.upsert({
            applicationId: resolvedId,
            interviewId: interviewId,
            stageCode,
            actorUserId: userId,
            actorRole: assignedRole || roleCode,
            decision,
            remarks: remarks || null
        });
    }
    
    if (interviewId) {
        await InterviewEvaluation.update(
            { 
                decision, 
                recommendation: decision,
                remarks: remarks || null,
                qualitativeFeedback: remarks || null
            },
            { where: { interviewId, evaluatorId: userId } }
        );
    }

    // --- FDD Integration: Link approval to FddReport table for dashboard mapping ---
    if (stageCode === 'FDD_VERIFICATION' && decision === 'Approved') {
        const assignment = await db.FddAssignment.findOne({ where: { applicationId: resolvedId } });
        if (assignment) {
            // Find latest audit report document
            const lastReportDoc = await db.OnboardingDocument.findOne({
                where: { applicationId: resolvedId, documentType: 'FDD Final Audit Report' },
                order: [['createdAt', 'DESC']]
            });
            
            // Parse structured recommendation/findings from remarks
            let recommendation = 'Recommended';
            let findings = remarks || 'Submission reviewed.';
            if (remarks?.includes('[RECOMMENDATION:')) {
                const parts = remarks.split('[RECOMMENDATION: ');
                if (parts[1]) {
                    recommendation = parts[1].split(']')[0];
                    findings = remarks.split('\nFindings: ')[1] || remarks.split(']')[1]?.trim() || remarks;
                }
            }

            let report = await db.FddReport.findOne({ where: { assignmentId: assignment.id } });
            
            if (report) {
                await report.update({
                    verifiedAt: new Date(),
                    verifiedBy: userId
                });
            } else {
                await db.FddReport.create({
                    assignmentId: assignment.id,
                    reportDocumentId: lastReportDoc?.id || null,
                    findings,
                    recommendation,
                    verifiedAt: new Date(),
                    verifiedBy: userId,
                    submittedBy: userId // Admin submitted it if no existing report
                });
            }
            
            await assignment.update({ status: 'Report Submitted' });

            // Bridge: Initialize LOI Records for the next stage (Moved from fdd.controller.ts for Admin Review flow)
            console.log(`[DEBUG] FDD Approved by Admin. Initializing LOI Records for Application: ${resolvedId}`);
            const [loiReq] = await db.LoiRequest.findOrCreate({
                where: { applicationId: resolvedId },
                defaults: { status: 'Pending Approval', requestedBy: userId }
            });

            const nextRoles = ['DD Head', 'NBH'];
            await Promise.all(nextRoles.map(async (role) => {
                await db.LoiApproval.findOrCreate({
                    where: { requestId: loiReq.id, approverRole: role },
                    defaults: { action: 'Pending', level: 1 }
                });
            }));
            console.log(`[DEBUG] LOI Records initialized for ${nextRoles.join(', ')}`);
        }
    }

    // Evaluate Policy via Centralized Service (FIXED unique user count)
    const evaluation = await WorkflowService.evaluateStagePolicy(resolvedId, stageCode);
    
    const hasRejection = decision === 'Rejected';
    const hasAnyApproval = (evaluation.approvedCount || 0) > 0;
    let statusUpdated = false;

    // Do NOT immediately reject on a single rejection.
    // Move forward when policy is met and at least one approver approved.
    if (evaluation.policyMet && !hasAnyApproval) {
        const application = await db.Application.findByPk(resolvedId);
        if (application) {
            await WorkflowService.transitionApplication(application, APPLICATION_STATUS.REJECTED, userId, {
                reason: remarks || `Rejected during ${stageCode} stage.`,
                stage: APPLICATION_STAGES.REJECTED
            });
            statusUpdated = true;
        }
    } else if (evaluation.policyMet && hasAnyApproval) {
        const application = await db.Application.findByPk(resolvedId);
        if (application) {
            let targetStatus = nextStatus;
            let targetStage = nextStage;
            let targetProgress = nextProgress;

            // Consolidated Parallel Track Logic (SRS v2.0 Section 1.1.2 Alignment)
            if (stageCode === 'ARCHITECTURE_WORK') {
                await application.update({ architectureStatus: 'COMPLETED' });
                targetStatus = undefined; 
                targetStage = 'Architecture Work';
                targetProgress = application.progressPercentage || 80;
                statusUpdated = true;
            } else if (stageCode === 'STATUTORY_WORK') {
                await application.update({ statutoryStatus: 'COMPLETED' });
                targetStatus = APPLICATION_STATUS.LOA_PENDING;
                targetStage = 'Statutory Work';
                targetProgress = 85;
            } else if (stageCode === 'LOA_APPROVAL') {
                targetStatus = APPLICATION_STATUS.LOA_ISSUED;
                targetStage = 'LOA';
                targetProgress = 95;
            } else if (stageCode === 'LOI_APPROVAL') {
                targetStatus = APPLICATION_STATUS.SECURITY_DETAILS;
                targetStage = APPLICATION_STAGES.LOI;
                targetProgress = typeof nextProgress === 'number' ? nextProgress : 78;
            }

            if (targetStatus) {
                await WorkflowService.transitionApplication(application, targetStatus, userId, {
                    reason: remarks || `Policy satisfied for ${stageCode}. Moving to next sequential step.`,
                    stage: targetStage,
                    progressPercentage: targetProgress
                });
                statusUpdated = true;
            }
        }
    } else {
        // --- SEQUENTIAL NOTIFICATION TRIGGER ---
        // If policy is NOT yet met (e.g. DD Head approved, waiting for NBH), 
        // we still need to trigger notifications for the NEXT person in the sequence.
        try {
            const { notifyStakeholdersOnTransition } = await import('../../common/utils/workflow-email-notifications.js');
            const portalBase = getFrontendBaseUrl();
            
            await notifyStakeholdersOnTransition(
                application.id,
                'application',
                application.currentStage || stageCode, // Notify for the CURRENT stage (to trigger resolveNextActors logic)
                {
                    code: application.applicationId,
                    dealerName: application.applicantName || 'Applicant',
                    dealerId: '',
                    actionUserFullName: 'Stakeholder', // Will be resolved by notifyStakeholders if needed
                    action: `Partial Approval: ${roleCode} approved ${stageCode}`,
                    remarks: remarks || 'Approval recorded. Waiting for next sequential stakeholder.',
                    link: `${portalBase}/applications/${application.id}`
                }
            );
        } catch (err) {
            console.error('[processStageDecision] Sequential notification failed:', err);
        }
    }

    return {
        success: true,
        message: statusUpdated
            ? (evaluation.policyMet && hasAnyApproval ? 'Policy satisfied. Stage complete.' : 'Rejected')
            : (hasRejection ? 'Rejection recorded. Waiting for remaining approvers.' : 'Approval recorded.'),
        policy,
        requiredRoles: evaluation.policy.requiredRoles,
        uniqueApprovalsByRole: evaluation.approvedRoles,
        hasAllRequiredRoleApprovals: evaluation.roleConditionMet,
        meetsMinApprovals: evaluation.meetsMinCount,
        statusUpdated
    };
};

const processInterviewApprovalDecision = async (params: {
    interviewId: string;
    decision: 'Approved' | 'Rejected';
    remarks?: string;
    userId: string;
    roleCode: string;
}) => {
    const { interviewId, decision, remarks, userId, roleCode } = params;
    const interview: any = await Interview.findByPk(interviewId);
    if (!interview) return { notFound: true };

    const stageCode = interviewStageCode(interview.level);
    
    // Ensure policy exists for interviews
    await ensureInterviewPolicy(interview.level);

    const nextStatusMap: any = { 1: 'Level 1 Approved', 2: 'Level 2 Approved', 3: 'FDD Verification' };
    const nextStageMap: any = { 1: APPLICATION_STAGES.LEVEL_1_APPROVED, 2: APPLICATION_STAGES.LEVEL_2_APPROVED, 3: APPLICATION_STAGES.FDD };
    const progressMap: any = { 1: 40, 2: 55, 3: 65 };
    
    const result = await processStageDecision({
        applicationId: interview.applicationId,
        stageCode,
        decision,
        remarks,
        userId,
        roleCode,
        interviewId,
        nextStatus: nextStatusMap[interview.level] || 'Approved',
        nextStage: nextStageMap[interview.level] || APPLICATION_STAGES.APPROVED,
        nextProgress: progressMap[interview.level]
    });

    if (result.statusUpdated) {
        await interview.update({ status: 'Completed', outcome: decision === 'Approved' ? 'Selected' : 'Rejected' });
    }

    return result;
};

// --- Questionnaires ---

export const getQuestionnaire = async (req: Request, res: Response) => {
    try {
        const { version } = req.query;
        const where: any = { isActive: true };
        if (version) where.version = version;

        const questionnaire = await Questionnaire.findOne({
            where,
            include: [{ model: QuestionnaireQuestion, as: 'questions' }],
            order: [['createdAt', 'DESC']] // GET latest if no version
        });

        res.json({ success: true, data: questionnaire });
    } catch (error) {
        console.error('Get questionnaire error:', error);
        res.status(500).json({ success: false, message: 'Error fetching questionnaire' });
    }
};

export const submitQuestionnaireResponse = async (req: AuthRequest, res: Response) => {
    try {
        const { applicationId, questionnaireId, responses } = req.body;
        
        const application = await db.Application.findOne({
            where: { applicationId }
        });
        
        if (!application) return res.status(404).json({ success: false, message: 'Application not found' });

        let totalWeightedScore = 0;

        for (const resp of responses) {
            await QuestionnaireResponse.create({
                applicationId,
                questionnaireId,
                questionId: resp.questionId,
                responseValue: resp.responseValue,
                attachmentUrl: resp.attachmentUrl
            });

            const question = await QuestionnaireQuestion.findByPk(resp.questionId, {
                include: [{ model: db.QuestionnaireOption, as: 'questionOptions' }]
            });

            if (question) {
                let questionScore = 0;
                if (question.questionOptions && question.questionOptions.length > 0) {
                    const selectedOption = question.questionOptions.find((opt: any) => opt.optionText === resp.responseValue);
                    if (selectedOption) {
                        questionScore = selectedOption.score;
                    }
                } else if (!isNaN(Number(resp.responseValue))) {
                    questionScore = Number(resp.responseValue);
                }

                totalWeightedScore += (questionScore * (question.weight || 1));
            }
        }

        await QuestionnaireScore.upsert({
            applicationId,
            questionnaireId,
            score: totalWeightedScore,
            maxScore: 100,
            status: 'Completed'
        });

        try {
            const loc =
                (application as any).preferredLocation || (application as any).city || 'your preferred location';
            await EmailService.sendQuestionnaireAckEmail(
                application.email,
                application.applicantName || 'Applicant',
                loc,
                application.applicationId || applicationId
            );
        } catch (mailErr) {
            console.error('[submitQuestionnaireResponse] acknowledgement email:', mailErr);
        }

        res.status(201).json({ success: true, message: 'Responses submitted successfully', score: totalWeightedScore });
    } catch (error) {
        console.error('Submit response error:', error);
        res.status(500).json({ success: false, message: 'Error submitting responses' });
    }
};

// --- Interviews ---

export const scheduleInterview = async (req: AuthRequest, res: Response) => {
    try {
        console.log('---------------------------------------------------');
        console.log('Incoming Schedule Interview Request:', JSON.stringify(req.body, null, 2));
        const { applicationId, level, scheduledAt, type, location, participants } = req.body; // participants: [userId]

        // Parse level string (e.g., "level1") to integer if necessary
        const levelNum = typeof level === 'string' ? parseInt(level.replace(/\D/g, ''), 10) : level;
        console.log(`Parsed Level: ${level} -> ${levelNum}`);

        const _isUUID_si = /^[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(applicationId as string);
        const application = await db.Application.findOne({
            where: _isUUID_si ? { [Op.or]: [{ id: applicationId }, { applicationId: applicationId }] } : { applicationId: applicationId }
        });
        
        if (!application) return res.status(404).json({ success: false, message: 'Application not found' });

        const scheduledDateObj = new Date(scheduledAt);
        if (Number.isNaN(scheduledDateObj.getTime())) {
            return res.status(400).json({ success: false, message: 'Invalid scheduledAt value. Please provide a valid ISO datetime.' });
        }
        const scheduledAtIso = scheduledDateObj.toISOString();

        // Prevent duplicate interviews for the same level
        const existingInterview = await Interview.findOne({
            where: {
                applicationId: application.id,
                level: levelNum || 1,
                status: { [Op.ne]: 'Cancelled' }
            }
        });

        if (existingInterview) {
            return res.status(400).json({
                success: false,
                message: `An interview for Level ${levelNum || 1} is already ${existingInterview.status.toLowerCase()}.`
            });
        }

        console.log('Creating Interview record...');
        const interview = await Interview.create({
            applicationId: application.id,
            level: levelNum || 1, // Default to 1 if parsing fails
            scheduleDate: scheduledDateObj,
            interviewType: type,
            linkOrLocation: location,
            status: 'Scheduled',
            scheduledBy: req.user?.id
        });
        console.log('Interview created with ID:', interview.id);
        const notificationPromises: Promise<any>[] = [];

        // Note: WorkflowTransition relocated below participant insertion.

        // MOCK INTEGRATIONS
        // 1. Google Calendar Mock
        const { meetLink } = await ExternalMocksService.mockScheduleMeeting({
            type,
            scheduledAt: scheduledAtIso,
            applicationId
        });
        await interview.update({ linkOrLocation: meetLink });

        // 2. Transmitted via Centralized Notification Service (SRS §6.14.3)
        // Replacing direct mock call with production-ready service trigger
        const applicantPhone = application.mobileNumber || application.phone || '';
        notificationPromises.push(
            NotificationService.notify(null, application.email, {
                title: `Interview Scheduled: ${application.applicationId}`,
                message: `Dear ${application.applicantName}, your ${type} is scheduled.`,
                channels: applicantPhone ? ['email', 'whatsapp', 'system'] : ['email', 'system'],
                templateCode: 'INTERVIEW_SCHEDULED_APPLICANT',
                placeholders: {
                    applicantName: application.applicantName,
                    applicationId: application.applicationId,
                    type,
                    scheduledAt: formatIST(scheduledDateObj),
                    meetLink,
                    appLink: getProspectPortalUrl(application.id),
                    phone: applicantPhone,
                    ctaLabel: 'View application'
                }
            }).catch(err => console.error('Failed to notify applicant via WhatsApp/Email:', err))
        );

        let participantIds: string[] = Array.isArray(participants) ? participants : [];

        // Auto-fill participants from pre-assigned RequestParticipants if not provided
        if (participantIds.length === 0) {
            const preAssigned = await db.RequestParticipant.findAll({
                where: {
                    requestId: application.id,
                    requestType: 'application',
                    'metadata.interviewLevel': levelNum
                },
                attributes: ['userId']
            });
            participantIds = preAssigned.map((p: any) => p.userId);
        }
        participantIds = [...new Set(participantIds)];

        // Determine designated evaluators for this interview level.
        // Rule: exactly one user per required role is treated as evaluator; others are panelists.
        const requiredRolesForLevel = getRequiredRolesForInterviewLevel(levelNum || 1).map(normalizeRoleCode);
        const selectedUsers = participantIds.length > 0
            ? await User.findAll({ where: { id: { [Op.in]: participantIds } }, attributes: ['id', 'roleCode'] })
            : [];
        const selectedUserRoleMap = new Map(selectedUsers.map((u: any) => [u.id, normalizeRoleCode(u.roleCode)]));
        const evaluatorIds = new Set<string>();

        // Prefer users that are already stage-mapped for this level on RequestParticipant metadata.
        const stageMappedParticipants = await RequestParticipant.findAll({
            where: {
                requestId: application.id,
                requestType: 'application'
            },
            attributes: ['userId', 'metadata']
        });
        for (const rp of stageMappedParticipants as any[]) {
            const m = rp.metadata || {};
            const mappedForLevel =
                m.interviewLevel === (levelNum || 1) ||
                m.interviewLevel === String(levelNum || 1) ||
                m.allAssignments?.includes(levelNum || 1) ||
                m.allAssignments?.includes(String(levelNum || 1));
            if (mappedForLevel && participantIds.includes(rp.userId)) {
                evaluatorIds.add(rp.userId);
            }
        }

        for (const requiredRole of requiredRolesForLevel) {
            if ([...evaluatorIds].some((id) => selectedUserRoleMap.get(id) === requiredRole)) continue;
            const match = participantIds.find((id) => selectedUserRoleMap.get(id) === requiredRole);
            if (match) evaluatorIds.add(match);
        }

        if (participantIds.length > 0) {
            console.log(`Processing ${participantIds.length} participants...`);
            // Processing participants concurrently
            await Promise.all(participantIds.map(async (userId) => {
                // 1. Add to Panel
                await InterviewParticipant.create({
                    interviewId: interview.id,
                    userId,
                    roleInPanel: evaluatorIds.has(userId) ? 'Evaluator' : 'Panelist'
                });

                // 2. Add as Request Participant for Collaboration
                console.log(`Adding user ${userId} to RequestParticipant...`);
                await RequestParticipant.findOrCreate({
                    where: {
                        requestId: application.id,
                        requestType: 'application',
                        userId
                    },
                    defaults: {
                        participantType: 'contributor', 
                        joinedMethod: 'interview'
                    }
                });
            }));
        }

        // Update Application Status (Moved after participants to ensure notification system can see the new participants)
        const statusMap: any = {
            1: 'Level 1 Interview Pending',
            2: 'Level 2 Interview Pending',
            3: 'Level 3 Interview Pending'
        };
        const newStatus = statusMap[levelNum] || 'Interview Scheduled';
        await WorkflowService.transitionApplication(application, newStatus, req.user?.id || null, {
            reason: `Interview Level ${levelNum} Scheduled`
        });

        // Ensure internal SLA timer runs even when status was already "Level N Interview Pending"
        const pipelineStage = PIPELINE_STAGE_LABEL_BY_OVERALL_STATUS[newStatus];
        if (pipelineStage && shouldTrackOnboardingSla(pipelineStage)) {
            await SLAService.startTrack({
                entityType: 'application',
                entityId: application.id,
                applicationId: application.id,
                stageName: pipelineStage
            }).catch((e) => console.error('[scheduleInterview] SLA start failed:', e));
        }

        // 3. Panelist notifications (Applicant is already notified above with INTERVIEW_SCHEDULED_APPLICANT)
        if (participantIds.length > 0) {
            for (const userId of participantIds) {
                notificationPromises.push(
                    (async () => {
                        const panelist = await User.findByPk(userId, { attributes: ['id', 'email', 'fullName', 'mobileNumber'] });
                        if (panelist) {
                            const pPhone = panelist.mobileNumber || null;
                            await NotificationService.notify(panelist.id, panelist.email, {
                                title: `Interview Assignment: ${application?.applicationId || 'New Case'}`,
                                message: `You have been assigned as a panelist for a ${type} with ${application?.applicantName || 'Applicant'}.`,
                                channels: pPhone ? ['email', 'system', 'whatsapp'] : ['email', 'system'],
                                templateCode: 'INTERVIEW_SCHEDULED_PANELIST',
                                placeholders: {
                                    panelistName: panelist.fullName,
                                    applicantName: application?.applicantName || 'Applicant',
                                    applicationId: application?.applicationId || '',
                                    type,
                                    scheduledAt: formatIST(scheduledDateObj),
                                    meetLink,
                                    appLink: `${getFrontendBaseUrl()}/applications/${application.id}`,
                                    phone: pPhone || '',
                                    ctaLabel: 'View application'
                                }
                            });
                        }
                    })().catch(err => console.error(`Failed to notify panelist (${userId}):`, err))
                );
            }
        }

        // We don't necessarily need to wait for all emails to finish before returning success to user
        // But for consistency and ensuring they are triggered, we'll wait with a timeout or just proceed
        // Let's use Promise.all but keep it out of the main critical path if we want to be ultra fast.
        // However, Promise.all already makes it much faster than sequential.
        await Promise.all(notificationPromises);

        await db.AuditLog.create({
            userId: req.user?.id || null,
            action: AUDIT_ACTIONS.INTERVIEW_SCHEDULED,
            entityType: 'application',
            entityId: application.id,
            newData: {
                interviewId: interview.id,
                interviewLevel: levelNum || 1,
                interviewType: type,
                scheduledAt: scheduledAtIso,
                linkOrLocation: meetLink || location || null,
                participantCount: participantIds.length
            }
        });

        console.log('Interview scheduling completed successfully.');
        res.status(201).json({ success: true, message: 'Interview scheduled successfully', data: interview });
    } catch (error) {
        console.error('CRITICAL ERROR in scheduleInterview:', error);
        // Log the full error object for inspection
        console.log(JSON.stringify(error, null, 2));
        res.status(500).json({ success: false, message: 'Error scheduling interview', error: String(error) });
    }
};

export const updateInterview = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params;
        const { status, scheduledAt, outcome } = req.body;

        const interview = await Interview.findByPk(id);
        if (!interview) return res.status(404).json({ success: false, message: 'Interview not found' });

        const oldStatus = interview.status;
        const oldScheduleDate = interview.scheduleDate;
        const oldOutcome = (interview as any).outcome ?? null;
        const updatePayload: any = {};

        if (typeof status !== 'undefined') updatePayload.status = status;
        if (typeof outcome !== 'undefined') updatePayload.outcome = outcome;
        if (typeof scheduledAt !== 'undefined') {
            const parsed = scheduledAt ? new Date(scheduledAt) : null;
            if (scheduledAt && parsed && Number.isNaN(parsed.getTime())) {
                return res.status(400).json({ success: false, message: 'Invalid scheduledAt value. Please provide a valid ISO datetime.' });
            }
            updatePayload.scheduleDate = parsed;
        }

        await interview.update(updatePayload);
        await interview.reload({
            include: [
                {
                    model: InterviewParticipant,
                    as: 'participants',
                    include: [{ model: User, as: 'user' }]
                }
            ]
        });

        const isCancelled = String(status || '').toLowerCase() === 'cancelled' && String(oldStatus || '').toLowerCase() !== 'cancelled';
        const isRescheduled = typeof scheduledAt !== 'undefined' && String(status || '').toLowerCase() !== 'cancelled';
        const eventType = isCancelled ? 'interview_cancelled' : (isRescheduled ? 'interview_rescheduled' : 'interview_updated');

        if (isRescheduled || isCancelled) {
            const application = await db.Application.findByPk(interview.applicationId);
            if (application) {
                const scheduledAtIso = interview.scheduleDate.toISOString();
                const type = interview.interviewType;
                const meetLink = interview.linkOrLocation;
                const notificationPromises: Promise<any>[] = [];

                // Notify Applicant
                notificationPromises.push(
                    NotificationService.notify(null, application.email, {
                        title: isCancelled ? `Interview Cancelled: ${application.applicationId}` : `Interview Rescheduled: ${application.applicationId}`,
                        message: isCancelled 
                            ? `Dear ${application.applicantName}, your ${type} has been cancelled.`
                            : `Dear ${application.applicantName}, your ${type} has been rescheduled to ${formatIST(interview.scheduleDate)}.`,
                        channels: application.mobileNumber ? ['email', 'whatsapp', 'system'] : ['email', 'system'],
                        templateCode: isCancelled ? 'INTERVIEW_CANCELLED_APPLICANT' : 'INTERVIEW_RESCHEDULED_APPLICANT',
                        placeholders: {
                            applicantName: application.applicantName,
                            applicationId: application.applicationId,
                            type,
                            scheduledAt: formatIST(interview.scheduleDate),
                            meetLink,
                            appLink: getProspectPortalUrl(application.id),
                            phone: application.mobileNumber || '',
                            ctaLabel: 'View Schedule'
                        }
                    }).catch(err => console.error('Failed to notify applicant of reschedule/cancellation:', err))
                );

                // Notify Panelists
                const participants = (interview as any).participants || [];
                for (const p of participants) {
                    const panelist = p.user;
                    if (panelist?.email) {
                        notificationPromises.push(
                            NotificationService.notify(panelist.id, panelist.email, {
                                title: isCancelled ? `Interview Cancelled: ${application.applicationId}` : `Interview Rescheduled: ${application.applicationId}`,
                                message: isCancelled
                                    ? `The ${type} for ${application.applicantName} has been cancelled.`
                                    : `The ${type} for ${application.applicantName} has been rescheduled to ${formatIST(interview.scheduleDate)}.`,
                                channels: panelist.mobileNumber ? ['email', 'system', 'whatsapp'] : ['email', 'system'],
                                templateCode: isCancelled ? 'INTERVIEW_CANCELLED_PANELIST' : 'INTERVIEW_RESCHEDULED_PANELIST',
                                placeholders: {
                                    panelistName: panelist.fullName,
                                    applicantName: application.applicantName,
                                    applicationId: application.applicationId,
                                    type,
                                    scheduledAt: formatIST(interview.scheduleDate),
                                    meetLink,
                                    appLink: `${getFrontendBaseUrl()}/applications/${application.id}`,
                                    phone: panelist.mobileNumber || '',
                                    ctaLabel: 'Open Assessment'
                                }
                            }).catch(err => console.error(`Failed to notify panelist (${panelist.id}) of reschedule/cancellation:`, err))
                        );
                    }
                }
                await Promise.all(notificationPromises);
            }
        }
        await db.AuditLog.create({
            userId: req.user?.id || null,
            action: AUDIT_ACTIONS.INTERVIEW_UPDATED,
            entityType: 'application',
            entityId: interview.applicationId,
            oldData: {
                interviewId: interview.id,
                interviewLevel: interview.level,
                status: oldStatus,
                scheduleDate: oldScheduleDate,
                outcome: oldOutcome
            },
            newData: {
                interviewId: interview.id,
                interviewLevel: interview.level,
                eventType,
                status: interview.status,
                scheduleDate: interview.scheduleDate,
                outcome: (interview as any).outcome ?? null
            }
        });

        res.json({ success: true, message: 'Interview updated successfully' });
    } catch (error) {
        console.error('Update interview error:', error);
        res.status(500).json({ success: false, message: 'Error updating interview' });
    }
};

export const submitEvaluation = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params; // Interview ID
        const { ktScore, feedback, recommendation, status } = req.body;

        const interview = await Interview.findByPk(id);
        if (!interview) return res.status(404).json({ success: false, message: 'Interview not found' });

        const evaluation = await InterviewEvaluation.create({
            interviewId: id,
            evaluatorId: req.user?.id,
            ktMatrixScore: ktScore,
            qualitativeFeedback: feedback,
            recommendation
        });

        // Auto update interview status if completed
        if (status === 'Completed') {
            await interview.update({ status: 'Completed', outcome: recommendation });
        }

        res.status(201).json({ success: true, message: 'Evaluation submitted successfully', data: evaluation });
    } catch (error) {
        console.error('Submit evaluation error:', error);
        res.status(500).json({ success: false, message: 'Error submitting evaluation' });
    }
};

export const submitKTMatrix = async (req: AuthRequest, res: Response) => {
    try {
        const { interviewId, criteriaScores, feedback, recommendation } = req.body;
        // criteriaScores: [{ criterionName, score, maxScore, weightage }]

        const interview = await Interview.findByPk(interviewId);
        if (!interview) return res.status(404).json({ success: false, message: 'Interview not found' });

        // Calculate total weighted score
        let totalWeightedScore = 0;
        const totalWeightage = criteriaScores.reduce((sum: number, item: any) => sum + item.weightage, 0);

        const ktMatrixScoresData = criteriaScores.map((item: any) => {
            const weightedScore = (item.score / item.maxScore) * item.weightage;
            totalWeightedScore += weightedScore;
            return {
                criterionName: item.criterionName,
                score: item.score,
                maxScore: item.maxScore,
                weightage: item.weightage,
                weightedScore
            };
        });

        // Check if evaluation exists for this user and interview, if so update, else create
        let evaluation = await InterviewEvaluation.findOne({
            where: { interviewId, evaluatorId: req.user?.id }
        });

        if (evaluation) {
            await evaluation.update({
                ktMatrixScore: totalWeightedScore,
                qualitativeFeedback: feedback,
                recommendation
            });
            // Remove old details to replace with new
            await db.KTMatrixScore.destroy({ where: { evaluationId: evaluation.id } });
        } else {
            evaluation = await InterviewEvaluation.create({
                interviewId,
                evaluatorId: req.user?.id,
                ktMatrixScore: totalWeightedScore,
                qualitativeFeedback: feedback,
                recommendation
            });
        }

        // Bulk create detailed scores
        const scoreRecords = ktMatrixScoresData.map((s: any) => ({
            ...s,
            evaluationId: evaluation?.id
        }));
        await db.KTMatrixScore.bulkCreate(scoreRecords);

        // Auto-process approval if recommendation is provided
        if (recommendation && req.user?.id && req.user?.roleCode) {
            const normalizedDecision = (['Recommended', 'Approved', 'Selected', 'Approve'].includes(recommendation))
                ? 'Approved' : 'Rejected';
            await processInterviewApprovalDecision({
                interviewId,
                decision: normalizedDecision,
                remarks: feedback,
                userId: req.user.id,
                roleCode: req.user.roleCode
            });
        }

        res.status(201).json({ success: true, message: 'KT Matrix submitted successfully', data: evaluation });
    } catch (error) {
        console.error('Submit KT Matrix error:', error);
        res.status(500).json({ success: false, message: 'Error submitting KT Matrix' });
    }
};

export const submitLevel2Feedback = async (req: AuthRequest, res: Response) => {
    try {
        const { interviewId, feedbackItems, recommendation, overallScore } = req.body;
        // feedbackItems: [{ type: 'Strategic Vision', comments: '...' }]

        const interview = await Interview.findByPk(interviewId);
        if (!interview) return res.status(404).json({ success: false, message: 'Interview not found' });

        // Check if evaluation exists for this user and interview, if so update, else create
        let evaluation = await InterviewEvaluation.findOne({
            where: { interviewId, evaluatorId: req.user?.id }
        });

        if (evaluation) {
            await evaluation.update({
                ktMatrixScore: overallScore, // Reusing this field for overall score (check if type matches)
                recommendation
            });
            // Remove old details to replace with new
            await db.InterviewFeedback.destroy({ where: { evaluationId: evaluation.id } });
        } else {
            evaluation = await InterviewEvaluation.create({
                interviewId,
                evaluatorId: req.user?.id,
                ktMatrixScore: overallScore,
                recommendation
            });
        }

        // Bulk create detailed qualitative feedback
        if (feedbackItems && feedbackItems.length > 0) {
            const feedbackRecords = feedbackItems.map((item: any) => ({
                evaluationId: evaluation?.id,
                feedbackType: item.type,
                comments: item.comments
            }));
            await db.InterviewFeedback.bulkCreate(feedbackRecords);
        }

        // Auto-process approval if recommendation is provided
        if (recommendation && req.user?.id && req.user?.roleCode) {
            const normalizedDecision = (['Recommended', 'Approved', 'Selected', 'Approve'].includes(recommendation))
                ? 'Approved' : 'Rejected';
            await processInterviewApprovalDecision({
                interviewId,
                decision: normalizedDecision,
                userId: req.user.id,
                roleCode: req.user.roleCode
            });
        }

        res.status(201).json({ success: true, message: 'Level 2 Feedback submitted successfully', data: evaluation });
    } catch (error) {
        console.error('Submit Level 2 Feedback error:', error);
        res.status(500).json({ success: false, message: 'Error submitting Level 2 Feedback' });
    }
};

// --- AI Summary ---

import { ExternalMocksService } from '../../common/utils/externalMocks.service.js';
import { getFrontendBaseUrl, getProspectPortalUrl } from '../../common/utils/frontendUrl.js';

export const generateAiSummary = async (req: AuthRequest, res: Response) => {
    try {
        const { applicationId } = req.params;

        // Find application UUID first
        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(applicationId as string);
        const app = await db.Application.findOne({ 
            where: isUUID ? { [Op.or]: [{ id: applicationId }, { applicationId }] } : { applicationId } 
        });
        if (!app) return res.status(404).json({ success: false, message: 'Application not found' });

        // 1. Fetch all interview evaluations for this application using UUID
        const interviews = await Interview.findAll({
            where: { applicationId: app.id },
            include: [{ model: InterviewEvaluation, as: 'evaluations' }]
        });

        const allEvaluations = interviews.flatMap((i: any) => i.evaluations || []);

        if (allEvaluations.length === 0) {
            return res.status(400).json({ success: false, message: 'No interview evaluations found to summarize' });
        }

        // 2. Map evaluations to a format Gemini (mock) understands
        const feedbackList = allEvaluations.map((e: any) => ({
            recommendation: e.recommendation,
            feedback: e.qualitativeFeedback
        }));

        // 3. Trigger Mock Gemini call
        const { summary } = await ExternalMocksService.mockGenerateAiSummary(applicationId as string, feedbackList);

        // 4. Save/Update AI Summary
        const [aiSummary, created] = await AiSummary.upsert({
            applicationId,
            summary,
            status: 'Generated',
            modelUsed: 'Gemini 1.5 Pro (Mock)'
        }, { returning: true });

        res.json({ success: true, data: aiSummary });
    } catch (error) {
        console.error('Generate AI summary error:', error);
        res.status(500).json({ success: false, message: 'Error generating AI summary' });
    }
};

export const getAiSummary = async (req: Request, res: Response) => {
    try {
        const { applicationId } = req.params;

        // Find application UUID first
        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(applicationId as string);
        const app = await db.Application.findOne({ 
            where: isUUID ? { [Op.or]: [{ id: applicationId }, { applicationId }] } : { applicationId } 
        });
        if (!app) return res.status(404).json({ success: false, message: 'Application not found' });

        const summary = await AiSummary.findOne({
            where: { applicationId: app.id },
            order: [['createdAt', 'DESC']]
        });

        if (!summary) {
            return res.json({ success: false, message: 'No AI Summary generated yet' });
        }

        res.json({ success: true, data: summary });
    } catch (error) {
        console.error('Get AI summary error:', error);
        res.status(500).json({ success: false, message: 'Error fetching AI summary' });
    }
};

export const getInterviews = async (req: Request, res: Response) => {
    try {
        const { applicationId } = req.params;
        
        // Find application UUID first
        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(applicationId as string);
        const app = await db.Application.findOne({ 
            where: isUUID ? { [Op.or]: [{ id: applicationId }, { applicationId }] } : { applicationId } 
        });
        if (!app) return res.status(404).json({ success: false, message: 'Application not found' });

        const interviews = await Interview.findAll({
            where: { applicationId: app.id },
            include: [
                {
                    model: InterviewParticipant,
                    as: 'participants',
                    separate: true,
                    include: [{ model: User, as: 'user' }]
                },
                {
                    model: InterviewEvaluation,
                    as: 'evaluations',
                    separate: true,
                    include: [{
                        model: User,
                        as: 'evaluator',
                        attributes: ['id', 'fullName', 'email'],
                        include: [{ model: Role, as: 'role', attributes: ['roleName', 'roleCode'] }]
                    },
                    {
                        model: db.InterviewFeedback,
                        as: 'feedbackDetails'
                    }]
                },
                {
                    model: User,
                    as: 'scheduler',
                    attributes: ['id', 'fullName', 'email', 'designation']
                }
            ],
            order: [['createdAt', 'DESC']]
        });
        res.json({ success: true, data: interviews });
    } catch (error) {
        console.error('Get interviews error:', error);
        res.status(500).json({ success: false, message: 'Error fetching interviews' });
    }
};

export const updateRecommendation = async (req: AuthRequest, res: Response) => {
    try {
        if (!req.user?.id || !req.user?.roleCode) {
            return res.status(401).json({ success: false, message: 'Unauthorized' });
        }
        const { interviewId, recommendation } = req.body; // recommendation: 'Recommended' | 'Not Recommended'
        const normalizedDecision = (['Recommended', 'Approved', 'Selected', 'Approve'].includes(recommendation))
            ? 'Approved' : 'Rejected';

        const result: any = await processInterviewApprovalDecision({
            interviewId,
            decision: normalizedDecision,
            remarks: req.body.remarks,
            userId: req.user.id,
            roleCode: req.user.roleCode
        });

        if (result.notFound) return res.status(404).json({ success: false, message: 'Interview not found' });
        if (result.forbidden) {
            return res.status(403).json({
                success: false,
                message: result.message || `Role ${result.currentRole} is not allowed to approve ${result.policy?.stageCode || 'this stage'}`
            });
        }

        res.json({
            success: true,
            message: 'Recommendation updated successfully',
            data: {
                evaluation: result.evaluation,
                stageCode: result.policy.stageCode,
                requiredRoles: result.requiredRoles,
                minApprovals: result.policy.minApprovals,
                approvedRoles: Array.from(result.uniqueApprovalsByRole),
                hasAllRequiredRoleApprovals: result.hasAllRequiredRoleApprovals,
                meetsMinApprovals: result.meetsMinApprovals
            }
        });
    } catch (error) {
        console.error('Update recommendation error:', error);
        res.status(500).json({ success: false, message: 'Error updating recommendation' });
    }
};

export const updateInterviewDecision = async (req: AuthRequest, res: Response) => {
    try {
        if (!req.user?.id || !req.user?.roleCode) {
            return res.status(401).json({ success: false, message: 'Unauthorized' });
        }
        const { interviewId, decision, remarks } = req.body; // decision: 'Approved' | 'Rejected'
        const normalizedDecision = (decision === 'Approved' || decision === 'Approve') ? 'Approved' : 'Rejected';
        const result: any = await processInterviewApprovalDecision({
            interviewId,
            decision: normalizedDecision,
            remarks,
            userId: req.user.id,
            roleCode: req.user.roleCode
        });

        if (result.notFound) return res.status(404).json({ success: false, message: 'Interview not found' });
        if (result.forbidden) {
            return res.status(403).json({
                success: false,
                message: result.message || `Role ${result.currentRole} is not allowed to approve ${result.policy?.stageCode || 'this stage'}`
            });
        }

        await db.AuditLog.create({
            userId: req.user?.id,
            action: 'UPDATED',
            entityType: 'interview',
            entityId: interviewId,
            newData: { decision, remarks }
        });

        res.json({
            success: true,
            message: `Recommendation ${normalizedDecision.toLowerCase()} successfully`,
            data: {
                stageCode: result.policy.stageCode,
                requiredRoles: result.requiredRoles,
                minApprovals: result.policy.minApprovals,
                approvedRoles: Array.from(result.uniqueApprovalsByRole),
                hasAllRequiredRoleApprovals: result.hasAllRequiredRoleApprovals,
                meetsMinApprovals: result.meetsMinApprovals
            }
        });
    } catch (error) {
        console.error('Update interview decision error:', error);
        res.status(500).json({ success: false, message: 'Error updating interview decision' });
    }
};

export const getStageApprovalPolicies = async (req: AuthRequest, res: Response) => {
    try {
        const policies = await StageApprovalPolicy.findAll({
            where: { isActive: true },
            order: [['stageCode', 'ASC']]
        });
        res.json({ success: true, data: policies });
    } catch (error) {
        console.error('Get stage approval policies error:', error);
        res.status(500).json({ success: false, message: 'Error fetching stage approval policies' });
    }
};

export const upsertStageApprovalPolicy = async (req: AuthRequest, res: Response) => {
    try {
        const { stageCode } = req.params;
        const { minApprovals, approvalMode, requiredRoles, isActive } = req.body;

        const [policy, created] = await StageApprovalPolicy.findOrCreate({
            where: { stageCode },
            defaults: {
                stageCode,
                minApprovals: minApprovals ?? 1,
                approvalMode: approvalMode ?? 'MIN_N',
                requiredRoles: requiredRoles ?? [],
                isActive: isActive ?? true
            }
        });

        if (!created) {
            await policy.update({
                minApprovals: minApprovals ?? policy.minApprovals,
                approvalMode: approvalMode ?? policy.approvalMode,
                requiredRoles: requiredRoles ?? policy.requiredRoles,
                isActive: isActive ?? policy.isActive
            });
        }

        res.json({ success: true, data: policy });
    } catch (error) {
        console.error('Upsert stage approval policy error:', error);
        res.status(500).json({ success: false, message: 'Error saving stage approval policy' });
    }
};

export const getInterviewApprovalStatus = async (req: AuthRequest, res: Response) => {
    try {
        const { interviewId } = req.params;
        const interview = await Interview.findByPk(interviewId);
        if (!interview) return res.status(404).json({ success: false, message: 'Interview not found' });

        const policy = await ensureInterviewPolicy(interview.level);
        const actions = await StageApprovalAction.findAll({
            where: {
                interviewId,
                stageCode: policy.stageCode
            },
            include: [{ model: User, as: 'actor', attributes: ['id', 'fullName', 'email', 'roleCode'] }],
            order: [['updatedAt', 'DESC']]
        });

        res.json({
            success: true,
            data: {
                interviewId,
                stageCode: policy.stageCode,
                minApprovals: policy.minApprovals,
                approvalMode: policy.approvalMode,
                requiredRoles: policy.requiredRoles || [],
                actions
            }
        });
    } catch (error) {
        console.error('Get interview approval status error:', error);
        res.status(500).json({ success: false, message: 'Error fetching interview approval status' });
    }
};
export const submitStageDecision = async (req: AuthRequest, res: Response) => {
    try {
        if (!req.user?.id || !req.user?.roleCode) return res.status(401).json({ success: false, message: 'Unauthorized' });
        
        const { applicationId, stageCode, decision, remarks, nextStatus, nextProgress } = req.body;

        const result: any = await processStageDecision({
            applicationId,
            stageCode,
            decision,
            remarks,
            userId: req.user.id,
            roleCode: req.user.roleCode,
            nextStatus,
            nextProgress
        });

        if (result.noPolicy) {
            // Fallback: If no policy, just update application status directly (legacy behavior)
            if (nextStatus) {
                const _isUUID_fb = /^[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(applicationId as string);
                const application = await db.Application.findOne({
                    where: _isUUID_fb ? { [Op.or]: [{ id: applicationId }, { applicationId: applicationId }] } : { applicationId: applicationId }
                });
                if (application) {
                    await WorkflowService.transitionApplication(application, nextStatus, req.user?.id || null, {
                        reason: 'Fallback Transition (No Policy)',
                        progressPercentage: nextProgress
                    });
                }
            }
            return res.json({ success: true, message: 'Status updated (No policy found)' });
        }

        if (result.forbidden) {
            return res.status(403).json({
                success: false,
                message: result.message || `Role ${result.currentRole} is not allowed to approve ${result.policy?.stageCode || stageCode}`
            });
        }

        res.json({
            success: true,
            message: result.statusUpdated ? `Stage ${stageCode} completed and status moved to ${nextStatus}` : `Decision recorded for ${stageCode}. Waiting for other approvers.`,
            data: {
                statusUpdated: result.statusUpdated,
                requiredRoles: result.requiredRoles,
                approvedRoles: Array.from(result.uniqueApprovalsByRole),
                meetsMinApprovals: result.meetsMinApprovals,
                hasAllRequiredRoleApprovals: result.hasAllRequiredRoleApprovals
            }
        });
    } catch (error) {
        console.error('Submit stage decision error:', error);
        res.status(500).json({ success: false, message: 'Error processing stage decision' });
    }
};
