import db from '../database/models/index.js';
const { Application, ApplicationStatusHistory, User, Dealer } = db;
import { syncApplicationProgress, PIPELINE_STAGE_LABEL_BY_OVERALL_STATUS } from '../common/utils/progress.js';
import { shouldTrackOnboardingSla } from '../common/config/slaStageCatalog.js';
import { SLAService } from './SLAService.js';
import {
    AUDIT_ACTIONS,
    APPLICATION_STAGES,
    OVERALL_STATUS_TO_DB_CURRENT_STAGE,
} from '../common/config/constants.js';
import { NotificationService } from './NotificationService.js';
import { pickApplicationAuditContext, safeAuditLogCreate } from './applicationAuditLog.service.js';
import { getFrontendBaseUrl, getProspectPortalUrl } from '../common/utils/frontendUrl.js';

export class WorkflowService {
    /**
     * Standardized method to transition an application's status
     * Handles: DB Update, Status History, Audit Log, and Progress Synchronization
     */
    static async transitionApplication(application: any, targetStatus: string, userId: string | null = null, metadata: any = {}) {
        const previousStatus = application.overallStatus;
        const previousStage = application.currentStage;
        const previousProgress = application.progressPercentage;
        const { reason, stage, progressPercentage, forceLog, transitionSource } = metadata;

        // Skip redundant history logging if status is identical (unless forced)
        if (targetStatus === previousStatus && !forceLog) {
            console.log(`[WorkflowService] Status already at ${targetStatus}. Skipping redundant log.`);
            return application;
        }

        const allowedStages = new Set<string>(Object.values(APPLICATION_STAGES) as string[]);

        /** Role-based gates (NBH, DD Head, …) — only these may override when explicitly passed. */
        const explicitStage =
            stage && allowedStages.has(stage as string) ? (stage as string) : null;
        /** Progress-tracker label (not necessarily a DB enum value). */
        const pipelineStageLabel = PIPELINE_STAGE_LABEL_BY_OVERALL_STATUS[targetStatus];
        /** Value allowed by Postgres enum `applications.currentStage`. */
        const dbStageFromOverallStatus = OVERALL_STATUS_TO_DB_CURRENT_STAGE[targetStatus];
        const stageForDbColumn =
            explicitStage ||
            (pipelineStageLabel && allowedStages.has(pipelineStageLabel) ? pipelineStageLabel : null) ||
            dbStageFromOverallStatus ||
            null;

        const updateData: any = {
            overallStatus: targetStatus,
            updatedAt: new Date()
        };

        if (stageForDbColumn && allowedStages.has(stageForDbColumn)) {
            updateData.currentStage = stageForDbColumn;
        }

        // Update progress percentage if explicitly provided
        if (progressPercentage !== undefined) {
            updateData.progressPercentage = progressPercentage;
        }

        const nextProgress =
            progressPercentage !== undefined ? Number(progressPercentage) : previousProgress;

        const contextBefore = pickApplicationAuditContext(application);

        // 1. Update Application Record
        await application.update(updateData);

        // 2. Log Status History
        await ApplicationStatusHistory.create({
            applicationId: application.id,
            previousStatus,
            newStatus: targetStatus,
            changedBy: userId,
            changeReason: reason || `Transitioned to ${targetStatus}`
        });

        const contextAfter = pickApplicationAuditContext(application);

        // 3. Audit log — non-fatal: must not roll back or block the transition
        await safeAuditLogCreate({
            userId: userId,
            action: AUDIT_ACTIONS.UPDATED,
            entityType: 'application',
            entityId: application.id,
            oldData: {
                status: previousStatus,
                stage: previousStage,
                progress: previousProgress,
                context: contextBefore,
            },
            newData: {
                status: targetStatus,
                stage: stageForDbColumn ?? previousStage,
                ...(pipelineStageLabel &&
                pipelineStageLabel !== (stageForDbColumn ?? previousStage)
                    ? { pipelineStage: pipelineStageLabel }
                    : {}),
                progress: nextProgress,
                reason: reason || `Transitioned to ${targetStatus}`,
                context: contextAfter,
                transitionSource: transitionSource || metadata?.source || 'WorkflowService.transitionApplication',
            },
        });

        // 4-6. Run non-fatal side effects in parallel to improve response time
        const sideEffects = async () => {
            try {
                const tasks = [];

                // SLA Tracking — use pipeline milestone label when available (matches slaStageCatalog)
                const slaPrevStage =
                    (previousStatus && PIPELINE_STAGE_LABEL_BY_OVERALL_STATUS[previousStatus]) ||
                    previousStage;
                const slaNextStage = pipelineStageLabel || stageForDbColumn;
                if (slaPrevStage && shouldTrackOnboardingSla(slaPrevStage)) {
                    tasks.push(
                        SLAService.stopTrack({
                            entityType: 'application',
                            entityId: application.id,
                            applicationId: application.id,
                            stageName: slaPrevStage
                        }).catch((e) => console.error('[WorkflowService] SLA stop failed:', e))
                    );
                }
                if (slaNextStage && shouldTrackOnboardingSla(slaNextStage)) {
                    tasks.push(
                        SLAService.startTrack({
                            entityType: 'application',
                            entityId: application.id,
                            applicationId: application.id,
                            stageName: slaNextStage
                        }).catch((e) => console.error('[WorkflowService] SLA start failed:', e))
                    );
                }

                // Progress Sync
                tasks.push(syncApplicationProgress(application.id, targetStatus).catch(e => console.error('[WorkflowService] progress sync failed:', e)));

                // Applicant-facing email notifications
                //
                // Earlier this block fired `ONBOARDING_STATUS_UPDATE` for EVERY transition,
                // which meant prospects got an email on internal events like "FDD Verification"
                // when the FDD agency uploaded their report. That leaked internal review
                // language to the applicant. We now allowlist only the milestones the
                // applicant genuinely needs to hear about; everything else is silent for
                // the prospect (internal teams still get their notifications via
                // `notifyStakeholdersOnTransition` below).
                //
                // Side note: stages that have their own dedicated trigger elsewhere
                // (interviews — INTERVIEW_SCHEDULED_APPLICANT in assessment.controller,
                // FDD docs request — FDD_DOCUMENT_REQUEST in fdd.controller, questionnaire,
                // etc.) are deliberately NOT in this allowlist to avoid duplicate emails.
                const APPLICANT_MILESTONE_TEMPLATES: Record<string, { template: string; ctaLabel: string }> = {
                    'Shortlisted': { template: 'APPLICANT_SHORTLISTED', ctaLabel: 'View application' },
                    'LOI Issued': { template: 'LOI_ISSUED', ctaLabel: 'View LOI' },
                    'LOA Issued': { template: 'LOA_ISSUED', ctaLabel: 'View LOA' },
                    'Dealer Code Generated': { template: 'DEALER_CODE_READY', ctaLabel: 'View application' },
                    'Dealer Code Generation': { template: 'DEALER_CODE_READY', ctaLabel: 'View application' },
                    'Rejected': { template: 'APPLICANT_REJECTED', ctaLabel: 'View application' },
                    'LOI Rejected': { template: 'APPLICANT_REJECTED', ctaLabel: 'View application' },
                    'LOA Rejected': { template: 'APPLICANT_REJECTED', ctaLabel: 'View application' },
                    'Disqualified': { template: 'APPLICANT_REJECTED', ctaLabel: 'View application' },
                };

                const milestone = APPLICANT_MILESTONE_TEMPLATES[targetStatus];
                if (milestone && application.email && !metadata.skipNotification) {
                    tasks.push((async () => {
                        const user = await User.findOne({
                            where: { email: application.email },
                            attributes: ['id', 'mobileNumber'],
                        });
                        const targetUserId = user ? user.id : null;

                        await NotificationService.notify(targetUserId, application.email, {
                            title: `Onboarding Update: ${targetStatus}`,
                            message: `Hi ${application.applicantName}, your application status has been updated to ${targetStatus}. ${reason || ''}`,
                            channels: ['email', 'whatsapp', 'system'],
                            templateCode: milestone.template,
                            placeholders: {
                                status: targetStatus,
                                applicantName: application.applicantName,
                                applicationId: application.applicationId,
                                location: application.preferredLocation || application.city || '',
                                rejectionReason: reason || 'N/A',
                                reason: reason || 'N/A',
                                salesCode: application.dealerCode?.salesCode || 'N/A',
                                serviceCode: application.dealerCode?.serviceCode || 'N/A',
                                // Applicant has no access to the internal admin screen,
                                // so route them through the prospect portal.
                                link: getProspectPortalUrl(application.id),
                                ctaLabel: milestone.ctaLabel,
                                phone: user?.mobileNumber || user?.phone || application?.mobileNumber || application?.phone || ''
                            },
                        });
                    })().catch(e => console.error('[WorkflowService] notification failed:', e)));
                } else if (!milestone) {
                    console.log(
                        `[WorkflowService] Skipping applicant email for non-milestone transition: ${targetStatus}`
                    );
                }

                // Stakeholder Notifications
                tasks.push((async () => {
                    const { notifyStakeholdersOnTransition } = await import('../common/utils/workflow-email-notifications.js');
                    let actionUserFullName = 'System';
                    if (userId) {
                        const actionUser = await User.findByPk(userId, { attributes: ['fullName'] });
                        if (actionUser) actionUserFullName = actionUser.fullName;
                    }

                    await notifyStakeholdersOnTransition(
                        application.id,
                        'application',
                        targetStatus,
                        {
                            code: application.applicationId,
                            dealerName: application.applicantName || 'Applicant',
                            dealerId: '',
                            actionUserFullName,
                            action: reason || `Transitioned to ${targetStatus}`,
                            remarks: reason || 'N/A',
                            link: `${getFrontendBaseUrl()}/applications/${application.id}`
                        }
                    );
                })().catch(e => console.error('[WorkflowService] stakeholder notify failed:', e)));

                await Promise.all(tasks);
            } catch (err) {
                console.error('[WorkflowService] Side effects runner failed:', err);
            }
        };

        // Trigger side effects but don't await them if they are truly non-fatal?
        // Actually, let's await them for now to ensure consistency, but in parallel.
        await sideEffects();

        console.log(`[WorkflowService] Transitioned Application ${application.applicationId} from ${previousStatus} to ${targetStatus}`);
        
        return application;
    }

    /**
     * Centralized policy evaluation for multi-role stage decisions.
     * Policy completion is response-driven (Approved/Rejected/Hold), while
     * sentiment is resolved by caller (e.g. proceed if at least one approval).
     */
    static async evaluateStagePolicy(applicationId: string, stageCode: string) {
        const policy = await db.StageApprovalPolicy.findOne({ where: { stageCode, isActive: true } });
        if (!policy) return { policyMet: true }; // No active policy means no restriction

        const requiredRoles: string[] = Array.isArray(policy.requiredRoles) ? policy.requiredRoles : [];
        const mode = policy.approvalMode || 'MIN_N';
        const minNeeded = policy.minApprovals || 1;
        
        // Fetch all submitted decisions for this stage.
        const actions = await db.StageApprovalAction.findAll({
            where: { applicationId, stageCode }
        });

        const uniqueResponders = new Set(actions.map((a: any) => a.actorUserId));
        const respondedRoles = new Set(actions.map((a: any) => a.actorRole));
        const approvedActions = actions.filter((a: any) => String(a.decision || '').toLowerCase() === 'approved');
        const uniqueApprovers = new Set(approvedActions.map((a: any) => a.actorUserId));
        const approvedRoles = new Set(approvedActions.map((a: any) => a.actorRole));
        
        // 1. Initial Gate: Super Admin bypass
        if (respondedRoles.has('Super Admin')) {
            return { policyMet: true, policy, overriddenBy: 'Super Admin' };
        }

        let roleConditionMet = false;
        
        switch (mode) {
            case 'ALL':
            case 'ROLE_MANDATORY':
                // Every required role must have responded (approve/reject/hold).
                roleConditionMet = requiredRoles.length === 0 || 
                    requiredRoles.every(role => respondedRoles.has(role));
                break;
            
            case 'MIN_N':
            default:
                // For MIN_N, response from at least one required role is sufficient.
                roleConditionMet = requiredRoles.length === 0 || 
                    requiredRoles.some(role => respondedRoles.has(role));
                break;
        }
        
        const meetsMinCount = uniqueResponders.size >= minNeeded;

        return {
            policyMet: roleConditionMet && meetsMinCount,
            policy,
            uniqueApprovers: Array.from(uniqueResponders),
            uniqueApprovedUsers: Array.from(uniqueApprovers),
            totalResponses: uniqueResponders.size,
            approvedCount: uniqueApprovers.size,
            approvedRoles: Array.from(approvedRoles),
            respondedRoles: Array.from(respondedRoles),
            roleConditionMet,
            meetsMinCount,
            mode
        };
    }
}
