import db from '../database/models/index.js';
import { CONSTITUTIONAL_STAGES, AUDIT_ACTIONS, DOCUMENT_TYPES, REQUEST_TYPES } from '../common/config/constants.js';
import { NotificationService } from './NotificationService.js';
import { writeWorkflowActivityWorknote } from '../common/utils/workflowWorknote.js';
import { getOffboardingAuditAction, formatOffboardingAction } from '../common/utils/offboardingWorkflow.utils.js';
import { mapConstitutionalChangeTypeToDealerProfile } from '../common/utils/constitutionalNormalize.js';
import { syncSlaOnStageTransition } from '../common/utils/slaWorkflowSync.js';
import { getFrontendBaseUrl } from '../common/utils/frontendUrl.js';

export class ConstitutionalWorkflowService {
    private static normalizeDocLabel(input: string): string {
        return String(input || '')
            .toLowerCase()
            .replace(/[^a-z0-9]+/g, ' ')
            .trim();
    }

    private static extractUploadedDocumentLabels(documents: any[]): string[] {
        if (!Array.isArray(documents)) return [];
        return documents
            .flatMap((doc: any) => [
                doc?.documentType,
                doc?.type,
                doc?.name,
                doc?.title,
                doc?.label,
                doc?.fileName,
                typeof doc === 'string' ? doc : null
            ])
            .filter((v: any) => typeof v === 'string' && v.trim().length > 0)
            .map((v: string) => this.normalizeDocLabel(v));
    }

    private static aliasesByRequirement(): Record<string, string[]> {
        return {
            [DOCUMENT_TYPES.GST_CERTIFICATE]: ['gst'],
            [DOCUMENT_TYPES.PAN_CARD]: ['pan', 'firm pan'],
            [DOCUMENT_TYPES.AADHAAR]: ['aadhaar', 'kyc'],
            [DOCUMENT_TYPES.CANCELLED_CHECK]: ['cancelled cheque', 'canceled cheque', 'cancelled check', 'cancelled'],
            [DOCUMENT_TYPES.OTHER]: ['declaration', 'authorization', 'authorisation'],
            [DOCUMENT_TYPES.PARTNERSHIP_DEED]: ['partnership deed', 'partnership agreement'],
            [DOCUMENT_TYPES.FIRM_REGISTRATION]: ['firm registration'],
            [DOCUMENT_TYPES.LLP_AGREEMENT]: ['llp agreement'],
            [DOCUMENT_TYPES.INCORPORATION_CERTIFICATE]: ['incorporation', 'coi'],
            [DOCUMENT_TYPES.MOA]: ['moa'],
            [DOCUMENT_TYPES.AOA]: ['aoa'],
            'Business Purchase Agreement (BPA)': ['business purchase agreement', 'bpa']
        };
    }

    private static docMatchesRequirement(doc: any, required: string): boolean {
        const aliases = this.aliasesByRequirement();
        const tokens = aliases[required] || [this.normalizeDocLabel(required)];
        const labels = [
            doc?.documentType,
            doc?.type,
            doc?.name,
            doc?.title,
            doc?.label,
            doc?.fileName
        ]
            .filter((v: any) => typeof v === 'string' && v.trim().length > 0)
            .map((v: string) => this.normalizeDocLabel(v));
        return tokens.some((token) => labels.some((label) => label.includes(token) || token.includes(label)));
    }

    /** Readiness aligned with relocation: uploaded vs verified vs rejected. */
    static getDocumentReadiness(targetConstitution: string, documents: any[]) {
        const checklist = this.getDocumentChecklist(targetConstitution);
        const docs = Array.isArray(documents) ? documents : [];
        const missingUploads: string[] = [];
        const pendingVerification: string[] = [];

        for (const required of checklist) {
            const matched = docs.filter((d) => this.docMatchesRequirement(d, required));
            if (!matched.length) {
                missingUploads.push(required);
                continue;
            }
            const hasVerified = matched.some((d) => String(d.status || '').toLowerCase() === 'verified');
            if (!hasVerified) {
                pendingVerification.push(required);
            }
        }

        return {
            totalRequired: checklist.length,
            missingUploads,
            pendingVerification
        };
    }

    static getMissingMandatoryDocuments(targetConstitution: string, documents: any[]): string[] {
        const readiness = this.getDocumentReadiness(targetConstitution, documents);
        return [...readiness.missingUploads, ...readiness.pendingVerification];
    }

    /**
     * Transitions a constitutional change request to a new stage
     */
    static async transitionRequest(request: any, targetStage: string, userId: string, options: any = {}) {
        const { action, status, remarks, userFullName, metadata = {} } = options;
        const sourceStage = request.currentStage;
        const actionLower = String(action || '').toLowerCase();
        
        // Audit Action resolution
        // 3. Create Audit Log using standardized mapper
        const { actionType } = metadata;
        const resolvedAuditAction = getOffboardingAuditAction(actionType || action || 'Approved', REQUEST_TYPES.CONSTITUTIONAL);

        const updatedTimeline = [
            ...(request.timeline || []),
            {
                stage: sourceStage, // Correctly Associate remark with the stage where action happened
                targetStage: targetStage,
                timestamp: new Date(),
                user: userFullName || 'System',
                action: action || `Moved to ${targetStage}`,
                remarks: remarks || ''
            }
        ];

        const resolvedStatus =
            status ||
            (targetStage === CONSTITUTIONAL_STAGES.COMPLETED
                ? 'Completed'
                : targetStage === CONSTITUTIONAL_STAGES.REJECTED
                  ? 'Rejected'
                  : targetStage === CONSTITUTIONAL_STAGES.REVOKED
                    ? 'Revoked'
                    : targetStage);

        const updateData: any = {
            currentStage: targetStage,
            status: resolvedStatus,
            progressPercentage: this.calculateProgress(targetStage),
            timeline: updatedTimeline,
            updatedAt: new Date()
        };

        // GAP CLOSURE: Reset joint approval metadata if sent back to or before ZM/RBM review stage
        // Controller uses "Sent back to …" (past tense). `sent` does not include substring `send`, so the old check never fired and joint-approval metadata was not cleared on send-back from ZBH.
        const isSendBack =
            /\b(send|sent)\s*back\b/i.test(String(action || '')) ||
            (actionLower.includes('send') && actionLower.includes('back'));
        const resetTargetStages = [CONSTITUTIONAL_STAGES.ASM_REVIEW, CONSTITUTIONAL_STAGES.ZM_RBM_REVIEW];

        
        if (isSendBack && resetTargetStages.includes(targetStage as any)) {
            const metadata = { ...(request.metadata || {}) };
            if (metadata.jointApprovals?.zmRbm) {
                console.log(`[ConstitutionalWorkflowService] Resetting joint approval metadata for ${request.requestId} on send-back to ${targetStage}`);
                delete metadata.jointApprovals.zmRbm;
                updateData.metadata = metadata;
                // Explicitly inform Sequelize that metadata has changed
                request.changed('metadata', true);
            }
        }


        await request.update(updateData);

        await syncSlaOnStageTransition({
            entityType: 'constitutional',
            entityId: request.id,
            fromStage: sourceStage,
            toStage: targetStage
        });

        if (targetStage === CONSTITUTIONAL_STAGES.COMPLETED && request.dealerId) {
            await ConstitutionalWorkflowService.syncDealerProfileConstitution(request, userId);
        }

        // Audit Log
        await db.ConstitutionalAudit.create({
            userId,
            constitutionalChangeId: request.id,
            action: formatOffboardingAction(resolvedAuditAction),
            remarks: remarks || '',
            details: { status: updateData.status, stage: sourceStage, targetStage: formatOffboardingAction(targetStage) }
        });

        // 5. Create Worknote for standardized communication trail (always when we have a user — empty modal comments still need a row)
        if (userId) {
            try {
                const body = String(remarks ?? '').trim() || 'No remarks entered.';
                await writeWorkflowActivityWorknote({
                    requestId: request.id,
                    requestType: 'constitutional',
                    userId: userId,
                    noteText: `${formatOffboardingAction(action || '')}: ${body}`,
                    noteType: isSendBack ? 'workflow' : 'internal'
                });
            } catch (wnErr) {
                console.error('[ConstitutionalWorkflowService] failed to write worknote:', wnErr);
            }
        }

        const dealerUser = await db.User.findByPk(request.dealerId, { attributes: ['id', 'email', 'fullName'] });
        if (dealerUser?.email) {
            const portalBase = getFrontendBaseUrl();
            const remarkText = String(remarks ?? '').trim() || 'N/A';
            
            const { notifyStakeholdersOnTransition } = await import('../common/utils/workflow-email-notifications.js');

            await notifyStakeholdersOnTransition(
                request.id,
                REQUEST_TYPES.CONSTITUTIONAL,
                targetStage,
                {
                    code: request.requestId,
                    dealerName: dealerUser.fullName || 'Dealer',
                    dealerId: dealerUser.id,
                    actionUserFullName: userFullName || 'System',
                    action: action || `Moved to ${targetStage}`,
                    remarks: remarkText,
                    link: `${portalBase}/constitutional-change/${request.id}`,
                    changeType: request.changeType
                }
            );
        }

        return request;
    }

    /**
     * SRS §12.2 — upon final approval, dealer master constitution must reflect the approved change.
     * Logs to constitutional audit trail and Work Notes (workflow) — important compliance record.
     */
    static async syncDealerProfileConstitution(request: any, actingUserId: string | null): Promise<void> {
        const nextConstitution = mapConstitutionalChangeTypeToDealerProfile(request.changeType);
        if (!nextConstitution) return;

        try {
            const user = await db.User.findByPk(request.dealerId, {
                include: [{ model: db.Dealer, as: 'dealerProfile' }]
            });
            const dealer = (user as any)?.dealerProfile;
            if (!dealer) {
                console.warn('[ConstitutionalWorkflowService] Completed CC request but no dealer profile for user', request.dealerId);
                return;
            }

            const previous = String(dealer.constitutionType || '').trim();
            if (previous === nextConstitution) {
                return;
            }

            await dealer.update({ constitutionType: nextConstitution });

            const auditRemarks = `Dealer master constitution updated from "${previous}" to "${nextConstitution}" via request ${request.requestId}.`;
            const auditDetails = {
                dealerProfileId: dealer.id,
                dealerUserId: request.dealerId,
                previousConstitution: previous,
                newConstitution: nextConstitution,
                requestId: request.requestId,
                changeType: request.changeType,
                stage: CONSTITUTIONAL_STAGES.COMPLETED
            };

            try {
                await db.ConstitutionalAudit.create({
                    userId: actingUserId || null,
                    constitutionalChangeId: request.id,
                    action: formatOffboardingAction(AUDIT_ACTIONS.UPDATED),
                    remarks: auditRemarks,
                    details: auditDetails
                });
            } catch (auditErr) {
                console.error('[ConstitutionalWorkflowService] Dealer sync audit log failed:', auditErr);
            }

            const worknoteUserId = actingUserId || String(request.dealerId);
            if (worknoteUserId) {
                try {
                    await writeWorkflowActivityWorknote({
                        requestId: request.id,
                        requestType: 'constitutional',
                        userId: worknoteUserId,
                        noteText: `Constitutional change ${request.requestId} completed: dealer constitution updated from "${previous}" to "${nextConstitution}".`,
                        noteType: 'workflow'
                    });
                } catch (wnErr) {
                    console.error('[ConstitutionalWorkflowService] dealer sync worknote failed:', wnErr);
                }
            }
        } catch (err) {
            console.error('[ConstitutionalWorkflowService] Failed to update dealer constitutionType:', err);
        }
    }

    /**
     * Calculates progress percentage based on stage
     */
    static calculateProgress(stage: string): number {
        const progress: Record<string, number> = {
            [CONSTITUTIONAL_STAGES.ASM_REVIEW]: 15,

            [CONSTITUTIONAL_STAGES.ZM_RBM_REVIEW]: 30,
            [CONSTITUTIONAL_STAGES.ZBH_REVIEW]: 45,
            [CONSTITUTIONAL_STAGES.LEAD_REVIEW]: 60,
            [CONSTITUTIONAL_STAGES.HEAD_REVIEW]: 75,
            [CONSTITUTIONAL_STAGES.NBH_APPROVAL]: 85,
            [CONSTITUTIONAL_STAGES.LEGAL_REVIEW]: 95,
            [CONSTITUTIONAL_STAGES.COMPLETED]: 100,
            [CONSTITUTIONAL_STAGES.REJECTED]: 100,
            [CONSTITUTIONAL_STAGES.REVOKED]: 100
        };
        return progress[stage] || 0;
    }

    /**
     * Returns mandatory document checklist based on target constitution
     * Per SRS Section 12.2.4
     */
    static getDocumentChecklist(targetConstitution: string): string[] {
        const commonDocs = [
            DOCUMENT_TYPES.GST_CERTIFICATE,
            DOCUMENT_TYPES.PAN_CARD,
            DOCUMENT_TYPES.AADHAAR,
            DOCUMENT_TYPES.CANCELLED_CHECK,
            DOCUMENT_TYPES.OTHER // Declaration/Authorization Letter
        ];

        if (targetConstitution.includes('Partnership')) {
            return [
                ...commonDocs,
                DOCUMENT_TYPES.PARTNERSHIP_DEED,
                'Business Purchase Agreement (BPA)',
                DOCUMENT_TYPES.FIRM_REGISTRATION
            ];
        }

        if (targetConstitution.includes('LLP')) {
            return [
                ...commonDocs,
                DOCUMENT_TYPES.INCORPORATION_CERTIFICATE,
                'Business Purchase Agreement (BPA)',
                DOCUMENT_TYPES.LLP_AGREEMENT
            ];
        }

        if (targetConstitution.includes('Private Limited')) {
            return [
                ...commonDocs,
                DOCUMENT_TYPES.MOA,
                DOCUMENT_TYPES.AOA,
                DOCUMENT_TYPES.INCORPORATION_CERTIFICATE,
                'Business Purchase Agreement (BPA)'
            ];
        }

        // Default/Proprietorship
        return commonDocs;
    }
}
