import { randomUUID } from 'crypto';
import { Response } from 'express';
import db from '../../database/models/index.js';
const { ConstitutionalChange, ConstitutionalAudit, Outlet, User, Worknote, Dealer, Application, District, StageApprovalPolicy, RequestParticipant, DealerCode } = db;
import { NotificationService } from '../../services/NotificationService.js';
import { Op } from 'sequelize';
import { AuthRequest } from '../../types/express.types.js';
import {
    CONSTITUTIONAL_STAGES,
    AUDIT_ACTIONS,
    ROLES,
    CONSTITUTIONAL_STRUCTURE_TARGET_OPTIONS
} from '../../common/config/constants.js';
import { ConstitutionalWorkflowService } from '../../services/ConstitutionalWorkflowService.js';
import { NomenclatureService } from '../../common/utils/nomenclature.js';
import { ParticipantService } from '../../services/ParticipantService.js';
import { resolveEntityUuidByType } from '../../common/utils/requestResolver.js';
import {
    isRegisteredConstitutionalChangeType,
    normalizeToConstitutionalChangeType
} from '../../common/utils/constitutionalNormalize.js';
import { OFFBOARDING_ACTIONS, REQUEST_TYPES } from '../../common/config/constants.js';
import { validateOffboardingAction, getPreviousStage } from '../../common/utils/offboardingWorkflow.utils.js';
import { writeWorkflowActivityWorknote } from '../../common/utils/workflowWorknote.js';
import { notifyConstitutionalSubmittedEmails } from '../../common/utils/workflow-email-notifications.js';
import { getFrontendBaseUrl } from '../../common/utils/frontendUrl.js';

const STRUCTURE_TARGET_VALUES = new Set<string>(
    CONSTITUTIONAL_STRUCTURE_TARGET_OPTIONS.map((o) => o.value as string)
);
const CLOSED_CONSTITUTIONAL_STATUSES = ['Completed', 'Closed', 'Rejected', 'Revoked'];

const CONSTITUTIONAL_DOC_REVIEW_ROLES = new Set<string>([
    ROLES.DD_LEAD,
    ROLES.DD_HEAD,
    ROLES.NBH,
    ROLES.LEGAL_ADMIN,
    ROLES.DD_ADMIN,
    ROLES.SUPER_ADMIN
]);

const ensureDocumentIds = (documents: any[]): any[] =>
    (Array.isArray(documents) ? documents : []).map((doc) =>
        doc?.id ? doc : { ...doc, id: randomUUID() }
    );

const findConstitutionalDocumentIndex = (documents: any[], documentId: string): number => {
    if (!Array.isArray(documents)) return -1;
    let idx = documents.findIndex((d) => d?.id === documentId);
    if (idx >= 0) return idx;
    const asNum = Number(documentId);
    if (!Number.isNaN(asNum) && asNum >= 0 && asNum < documents.length) return asNum;
    return documents.findIndex((d) => String(d?.docNumber) === documentId);
};

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

export const getMeta = async (_req: AuthRequest, res: Response) => {
    try {
        res.json({
            success: true,
            structureTargets: CONSTITUTIONAL_STRUCTURE_TARGET_OPTIONS.map((o) => ({ value: o.value, label: o.label }))
        });
    } catch (error) {
        console.error('Constitutional meta error:', error);
        res.status(500).json({ success: false, message: 'Error loading options' });
    }
};

export const submitRequest = async (req: AuthRequest, res: Response) => {
    try {
        if (!req.user) return res.status(401).json({ success: false, message: 'Unauthorized' });

        const {
            outletId,
            changeType,
            reason,
            description,
            currentConstitution,
            newPartnersDetails,
            shareholdingPattern,
            forDealerUserId
        } = req.body;

        const remarksText = String(reason ?? description ?? '').trim();
        if (!remarksText) {
            return res.status(400).json({ success: false, message: 'Reason / description is required' });
        }

        const resolvedChangeType = normalizeToConstitutionalChangeType(String(changeType || '').trim());
        if (!resolvedChangeType || !isRegisteredConstitutionalChangeType(resolvedChangeType)) {
            return res.status(400).json({
                success: false,
                message: 'Invalid constitutional change type. Use a value returned from GET /constitutional-change/meta.'
            });
        }

        const isDealerRole = req.user.roleCode === ROLES.DEALER;
        const internalTargetUserId = forDealerUserId ? String(forDealerUserId).trim() : '';

        if (isDealerRole && internalTargetUserId && internalTargetUserId !== req.user.id) {
            return res.status(403).json({
                success: false,
                message: 'Dealers cannot create a constitutional request for another account.'
            });
        }

        if (!isDealerRole && !internalTargetUserId) {
            return res.status(400).json({
                success: false,
                message: 'Select the dealer this request is for (forDealerUserId).' 
            });
        }

        let dealerUserId = req.user.id;
        let subjectDealerProfile: any = null;

        if (!isDealerRole) {
            const targetUser = await User.findByPk(internalTargetUserId, {
                include: [{ model: Dealer, as: 'dealerProfile' }]
            });
            if (!targetUser || targetUser.roleCode !== ROLES.DEALER) {
                return res.status(400).json({ success: false, message: 'Invalid dealer user selected.' });
            }
            if (!(targetUser as any).dealerProfile) {
                return res.status(400).json({
                    success: false,
                    message: 'Selected user has no dealer profile.'
                });
            }
            dealerUserId = targetUser.id;
            subjectDealerProfile = (targetUser as any).dealerProfile;
        } else {
            const selfUser = await User.findByPk(req.user.id, {
                include: [{ model: Dealer, as: 'dealerProfile' }]
            });
            subjectDealerProfile = (selfUser as any)?.dealerProfile || null;
            if (!subjectDealerProfile) {
                return res.status(400).json({ success: false, message: 'Dealer profile not found for this account.' });
            }
        }

        const profileConstitutionRaw = subjectDealerProfile?.constitutionType;
        const resolvedCurrentFromBody = normalizeToConstitutionalChangeType(
            currentConstitution != null ? String(currentConstitution) : ''
        );
        const resolvedCurrentFromProfile = normalizeToConstitutionalChangeType(
            profileConstitutionRaw != null ? String(profileConstitutionRaw) : ''
        );

        let resolvedCurrent = resolvedCurrentFromBody || resolvedCurrentFromProfile;
        if (!resolvedCurrent) {
            if (String(profileConstitutionRaw || '').trim()) {
                return res.status(400).json({
                    success: false,
                    message: `Dealer profile constitution "${profileConstitutionRaw}" is not recognized. Update constitution type in dealer master data.`
                });
            }
            resolvedCurrent = normalizeToConstitutionalChangeType('Proprietorship')!;
        }

        if (
            STRUCTURE_TARGET_VALUES.has(resolvedChangeType) &&
            STRUCTURE_TARGET_VALUES.has(resolvedCurrent) &&
            resolvedChangeType === resolvedCurrent
        ) {
            return res.status(400).json({
                success: false,
                message: 'Proposed constitution must differ from the current constitution.'
            });
        }

        let resolvedOutletId: string | null = outletId ? String(outletId) : null;
        if (resolvedOutletId) {
            const outlet = await Outlet.findByPk(resolvedOutletId);
            if (!outlet || String(outlet.dealerId) !== String(dealerUserId)) {
                return res.status(400).json({
                    success: false,
                    message: 'Selected outlet does not belong to the chosen dealer user.'
                });
            }
        } else {
            const firstOutlet = await Outlet.findOne({
                where: { dealerId: dealerUserId },
                order: [['createdAt', 'ASC']]
            });
            resolvedOutletId = firstOutlet ? String(firstOutlet.id) : null;
        }

        const requestId = await NomenclatureService.generateConstitutionalChangeId();

        const dealerProfileStatus = String(subjectDealerProfile?.status || '').toLowerCase();
        const isDealerActive = !dealerProfileStatus || dealerProfileStatus === 'active';
        if (!isDealerActive || !subjectDealerProfile?.onboardedAt) {
            return res.status(400).json({
                success: false,
                message: 'Constitutional change can be initiated only for active onboarded dealers.'
            });
        }

        const existingOpenRequest = await ConstitutionalChange.findOne({
            where: {
                dealerId: dealerUserId,
                status: { [Op.notIn]: CLOSED_CONSTITUTIONAL_STATUSES }
            },
            attributes: ['id', 'requestId', 'status', 'currentStage'],
            order: [['createdAt', 'DESC']]
        });
        if (existingOpenRequest) {
            return res.status(409).json({
                success: false,
                message: `Open constitutional request ${existingOpenRequest.requestId} already exists at ${existingOpenRequest.currentStage}. Complete it before creating a new one.`
            });
        }

        const metadata = {
            newPartnersDetails,
            shareholdingPattern,
            currentConstitution: resolvedCurrent,
            submittedByUserId: req.user.id,
            submittedByRole: req.user.roleCode,
            createdOnBehalfOfDealer: !isDealerRole
        };

        const request = await ConstitutionalChange.create({
            requestId,
            outletId: resolvedOutletId,
            dealerId: dealerUserId,
            changeType: resolvedChangeType,
            oldValue: resolvedCurrent,
            newValue: resolvedChangeType,
            description: remarksText,
            currentConstitution: resolvedCurrent,
            currentStage: CONSTITUTIONAL_STAGES.ASM_REVIEW,
            status: 'ASM Review',
            progressPercentage: ConstitutionalWorkflowService.calculateProgress(CONSTITUTIONAL_STAGES.ASM_REVIEW),

            metadata,
            documents: [],
            timeline: [{
                stage: 'Submitted',
                timestamp: new Date(),
                user: req.user.fullName,
                action: isDealerRole ? 'Request submitted' : 'Request submitted (on behalf of dealer)',
                remarks: remarksText
            }]
        });

        await db.AuditLog.create({
            userId: req.user.id,
            action: AUDIT_ACTIONS.CREATED,
            entityType: 'constitutional_change',
            entityId: request.id
        });

        await ConstitutionalAudit.create({
            userId: req.user.id,
            constitutionalChangeId: request.id,
            action: AUDIT_ACTIONS.CREATED,
            remarks: remarksText || 'Constitutional change request submitted',
            details: { stage: CONSTITUTIONAL_STAGES.ASM_REVIEW, requestId: request.requestId }

        });

        res.status(201).json({
            success: true,
            message: 'Constitutional change request submitted successfully',
            requestId: request.requestId
        });

        // Run participant assignment + notifications asynchronously to keep API responsive.
        // With Redis disabled, email sending is synchronous and can otherwise block for many seconds.
        void (async () => {
            try {
                await ParticipantService.assignConstitutionalParticipants(request.id);
                const displayName = (await User.findByPk(dealerUserId, { attributes: ['fullName'] }))?.fullName || 'Dealer';
                await notifyConstitutionalSubmittedEmails(request, displayName);
            } catch (e) {
                console.error('Error assigning participants or sending constitutional submit emails:', e);
            }
        })();
    } catch (error) {
        console.error('Submit constitutional change error:', error);
        res.status(500).json({ success: false, message: 'Error submitting request' });
    }
};

export const getRequests = async (req: AuthRequest, res: Response) => {
    try {
        if (!req.user) return res.status(401).json({ success: false, message: 'Unauthorized' });

        const where: any = {};
        if (req.user.roleCode === 'Dealer') {
            where.dealerId = req.user.id;
        } else {
            const { status } = req.query;
            if (status) {
                if (status === 'pending') {
                    where.status = { [Op.notIn]: ['Completed', 'Closed', 'Rejected', 'Revoked'] };
                } else if (status === 'completed') {
                    where.status = { [Op.in]: ['Completed', 'Closed'] };
                } else if (status === 'rejected') {
                    where.status = { [Op.in]: ['Rejected', 'Revoked'] };
                } else {
                    where.status = status;
                }
            }
        }

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

        const { count, rows: requests } = await ConstitutionalChange.findAndCountAll({
            where,
            include: [
                { model: Outlet, as: 'outlet' },
                { 
                    model: User, 
                    as: 'dealer', 
                    attributes: ['fullName'],
                    include: [{ model: Dealer, as: 'dealerProfile', include: [{ model: DealerCode, as: 'dealerCode' }] }]
                }
            ],
            order: [['createdAt', 'DESC']],
            limit,
            offset,
            distinct: true
        });

        res.json({ 
            success: true, 
            requests,
            meta: {
                total: count,
                totalPages: Math.ceil(count / limit),
                currentPage: page,
                limit,
                stats: {
                    total: count,
                    pending: await ConstitutionalChange.count({ where: { ...where, status: { [Op.notIn]: ['Completed', 'Closed', 'Rejected', 'Revoked'] } } }),
                    completed: await ConstitutionalChange.count({ where: { ...where, status: { [Op.in]: ['Completed', 'Closed'] } } }),
                    rejected: await ConstitutionalChange.count({ where: { ...where, status: { [Op.in]: ['Rejected', 'Revoked'] } } })
                }
            }
        });
    } catch (error) {
        console.error('Get constitutional changes error:', error);
        res.status(500).json({ success: false, message: 'Error fetching requests' });
    }
};

export const getRequestById = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params;
        const resolvedId = await resolveConstitutionalUuid(String(id));

        const request = await ConstitutionalChange.findOne({
            where: { id: resolvedId },
            include: [
                { model: Outlet, as: 'outlet' },
                { 
                    model: User, 
                    as: 'dealer', 
                    attributes: ['id', 'fullName', 'email', 'roleCode'],
                    include: [
                        {
                            model: db.Dealer,
                            as: 'dealerProfile',
                            attributes: [
                                'id',
                                'legalName',
                                'businessName',
                                'constitutionType',
                                'registeredAddress',
                                'onboardedAt',
                                'loiDate',
                                'loaDate',
                                'gstNumber',
                                'panNumber',
                                'dealerCodeId',
                                'applicationId',
                                'status'
                            ],
                            include: [
                                { model: db.DealerCode, as: 'dealerCode' },
                                {
                                    model: db.Application,
                                    as: 'application',
                                    include: [
                                        { model: db.District, as: 'district' },
                                        {
                                            model: db.LoiRequest,
                                            as: 'loiRequests',
                                            where: { [Op.or]: [{ status: 'approved' }, { status: 'Approved' }] },
                                            required: false
                                        },
                                        {
                                            model: db.LoaRequest,
                                            as: 'loaRequests',
                                            where: { [Op.or]: [{ status: 'approved' }, { status: 'Approved' }] },
                                            required: false
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                },
                { model: Worknote, as: 'worknotes' },
                { 
                    model: db.RequestParticipant, 
                    as: 'participants',
                    include: [{ model: User, as: 'user', attributes: ['id', 'fullName', 'email', 'roleCode'] }]
                }
            ]
        });

        if (!request) return res.status(404).json({ success: false, message: 'Request not found' });
        if (req.user?.roleCode === ROLES.DEALER && String(request.dealerId) !== String(req.user.id)) {
            return res.status(403).json({ success: false, message: 'Forbidden: You can only access your own request.' });
        }

        res.json({ success: true, request });
    } catch (error) {
        console.error('Get constitutional change details error:', error);
        res.status(500).json({ success: false, message: 'Error fetching details' });
    }
};

const STAGE_FLOW_FORWARD: Record<string, string> = {
    [CONSTITUTIONAL_STAGES.ASM_REVIEW]: CONSTITUTIONAL_STAGES.ZM_RBM_REVIEW,

    [CONSTITUTIONAL_STAGES.ZM_RBM_REVIEW]: CONSTITUTIONAL_STAGES.ZBH_REVIEW,
    [CONSTITUTIONAL_STAGES.ZBH_REVIEW]: CONSTITUTIONAL_STAGES.LEAD_REVIEW,
    [CONSTITUTIONAL_STAGES.LEAD_REVIEW]: CONSTITUTIONAL_STAGES.HEAD_REVIEW,
    [CONSTITUTIONAL_STAGES.HEAD_REVIEW]: CONSTITUTIONAL_STAGES.NBH_APPROVAL,
    [CONSTITUTIONAL_STAGES.NBH_APPROVAL]: CONSTITUTIONAL_STAGES.LEGAL_REVIEW,
    [CONSTITUTIONAL_STAGES.LEGAL_REVIEW]: CONSTITUTIONAL_STAGES.COMPLETED
};

/** SRS §12.2.3 — return to previous review stage */
const STAGE_FLOW_BACK: Record<string, string | undefined> = {
    [CONSTITUTIONAL_STAGES.ZM_RBM_REVIEW]: CONSTITUTIONAL_STAGES.ASM_REVIEW,

    [CONSTITUTIONAL_STAGES.ZBH_REVIEW]: CONSTITUTIONAL_STAGES.ZM_RBM_REVIEW,
    [CONSTITUTIONAL_STAGES.LEAD_REVIEW]: CONSTITUTIONAL_STAGES.ZBH_REVIEW,
    [CONSTITUTIONAL_STAGES.HEAD_REVIEW]: CONSTITUTIONAL_STAGES.LEAD_REVIEW,
    [CONSTITUTIONAL_STAGES.NBH_APPROVAL]: CONSTITUTIONAL_STAGES.HEAD_REVIEW,
    [CONSTITUTIONAL_STAGES.LEGAL_REVIEW]: CONSTITUTIONAL_STAGES.NBH_APPROVAL
};

const CONSTITUTIONAL_STAGE_POLICY_CODES: Record<string, string> = {
    [CONSTITUTIONAL_STAGES.ZM_RBM_REVIEW]: 'CONSTITUTIONAL_ZM_RBM_REVIEW'
};

const hasRoleInConfig = (rolesArray: string[] | undefined, role: string) => {
    if (!rolesArray || !role) return false;
    return rolesArray.includes(role);
};

const normalizeRoleKey = (rawRole: string) => {
    const role = String(rawRole || '').trim().toUpperCase();
    if (role === 'DD ZM' || role === 'ZM' || role === 'DD-ZM') return 'DD-ZM';
    return role;
};

const actionSuccessMessage = (raw: string): string => {
    const a = String(raw || '').trim().toLowerCase();
    if (a === 'reject') return 'Request rejected successfully';
    if (a === 'revoke') return 'Request revoked successfully';
    if (a.includes('send') && a.includes('back')) return 'Request sent back successfully';
    if (a === 'approve') return 'Request approved successfully';
    return 'Action completed successfully';
};

export const takeAction = async (req: AuthRequest, res: Response) => {
    try {
        if (!req.user) return res.status(401).json({ success: false, message: 'Unauthorized' });
        if (req.user.roleCode === ROLES.DEALER) {
            return res.status(403).json({ success: false, message: 'Dealers cannot perform review actions.' });
        }

        const { id } = req.params;
        const rawAction = String(req.body.action || '').trim();
        // Normalize to lowercase alphanumeric for robust comparison (handles "Send Back", "sendBack", "send-back")
        const actionNorm = rawAction.toLowerCase().replace(/[^a-z0-9]/g, '');
        /** Prefer `comments`; `remarks` accepted for parity with relocation / other modules */
        const comments = req.body.comments ?? req.body.remarks;
        const resolvedId = await resolveConstitutionalUuid(String(id));

        const request = await ConstitutionalChange.findOne({
            where: { id: resolvedId }
        });

        if (!request) return res.status(404).json({ success: false, message: 'Request not found' });
        const sourceStage = request.currentStage;
        const remarksTrim = String(comments || '').trim();

        const normalize = (s: string) => String(s || '').toLowerCase().replace(/[^a-z0-9]/g, '');
        const isReject = actionNorm === normalize(OFFBOARDING_ACTIONS.REJECT);
        const isRevoke = actionNorm === normalize(OFFBOARDING_ACTIONS.REVOKE);
        const isSendBack = actionNorm === normalize(OFFBOARDING_ACTIONS.SEND_BACK);
        const isApprove = actionNorm === normalize(OFFBOARDING_ACTIONS.APPROVE);

        const stageRoleMap: Record<string, string[]> = {
            [CONSTITUTIONAL_STAGES.ASM_REVIEW]: [ROLES.ASM, ROLES.SUPER_ADMIN],
            [CONSTITUTIONAL_STAGES.ZM_RBM_REVIEW]: [ROLES.DD_ZM, ROLES.RBM, ROLES.SUPER_ADMIN],
            [CONSTITUTIONAL_STAGES.ZBH_REVIEW]: [ROLES.ZBH, ROLES.SUPER_ADMIN],
            [CONSTITUTIONAL_STAGES.LEAD_REVIEW]: [ROLES.DD_LEAD, ROLES.SUPER_ADMIN],
            [CONSTITUTIONAL_STAGES.HEAD_REVIEW]: [ROLES.DD_HEAD, ROLES.SUPER_ADMIN],
            [CONSTITUTIONAL_STAGES.NBH_APPROVAL]: [ROLES.NBH, ROLES.SUPER_ADMIN],
            [CONSTITUTIONAL_STAGES.LEGAL_REVIEW]: [ROLES.LEGAL_ADMIN, ROLES.SUPER_ADMIN]
        };
        const allowedRoles = stageRoleMap[sourceStage] || [ROLES.SUPER_ADMIN];
        if (!allowedRoles.includes(req.user.roleCode as any)) {
            return res.status(403).json({
                success: false,
                message: `Role ${req.user.roleCode} cannot act at stage ${sourceStage}. Allowed: ${allowedRoles.join(', ')}`
            });
        }


        if (isReject) {
            await ConstitutionalWorkflowService.transitionRequest(request, CONSTITUTIONAL_STAGES.REJECTED, req.user.id, {
                action: 'Rejected',
                status: 'Rejected',
                remarks: comments,
                userFullName: req.user.fullName,
                metadata: { actionType: OFFBOARDING_ACTIONS.REJECT }
            });
            return res.json({ success: true, message: actionSuccessMessage(OFFBOARDING_ACTIONS.REJECT) });
        }

        if (isRevoke) {
            const validation = validateOffboardingAction(OFFBOARDING_ACTIONS.REVOKE, comments);
            if (!validation.valid) {
                return res.status(400).json({ success: false, message: validation.message });
            }

            await ConstitutionalWorkflowService.transitionRequest(request, CONSTITUTIONAL_STAGES.REVOKED, req.user.id, {
                action: 'Revoked',
                status: 'Revoked',
                remarks: comments,
                userFullName: req.user.fullName,
                metadata: { actionType: OFFBOARDING_ACTIONS.REVOKE }
            });
            return res.json({ success: true, message: actionSuccessMessage(OFFBOARDING_ACTIONS.REVOKE) });
        }

        if (isSendBack) {
            const validation = validateOffboardingAction(OFFBOARDING_ACTIONS.SEND_BACK, comments);
            if (!validation.valid) {
                return res.status(400).json({ success: false, message: validation.message });
            }

            const prevStage = getPreviousStage(REQUEST_TYPES.CONSTITUTIONAL, request.currentStage);
            if (!prevStage) {
                return res.status(400).json({ success: false, message: 'Cannot send back from this stage' });
            }

            await ConstitutionalWorkflowService.transitionRequest(request, prevStage, req.user.id, {
                action: `Sent back to ${prevStage}`,
                remarks: comments,
                userFullName: req.user.fullName,
                metadata: { actionType: OFFBOARDING_ACTIONS.SEND_BACK }
            });
            return res.json({ success: true, message: actionSuccessMessage(OFFBOARDING_ACTIONS.SEND_BACK) });
        }

        if (!isApprove) {
            return res.status(400).json({ success: false, message: 'Unsupported action. Use Approve, Reject, Send Back, or Revoke.' });
        }

        const missingDocs = ConstitutionalWorkflowService.getMissingMandatoryDocuments(
            request.changeType,
            request.documents || []
        );
        if (missingDocs.length > 0) {
            return res.status(400).json({
                success: false,
                message: `Mandatory documents missing for ${request.changeType}: ${missingDocs.join(', ')}. Upload all required documents before approval.`
            });
        }

        const isZmRbmJointStage = request.currentStage === CONSTITUTIONAL_STAGES.ZM_RBM_REVIEW;
        if (isZmRbmJointStage && isApprove) {

            const stageCode = CONSTITUTIONAL_STAGE_POLICY_CODES[request.currentStage];
            const policy = stageCode
                ? await StageApprovalPolicy.findOne({ where: { stageCode, isActive: true } })
                : null;
            const requiredRoles = (policy?.requiredRoles?.length ? policy.requiredRoles : ['DD-ZM', 'RBM'])
                .map((r: string) => normalizeRoleKey(r));
            const minApprovals = Math.max(
                Number(policy?.minApprovals || requiredRoles.length || 2),
                requiredRoles.length ? 1 : 2
            );

            const actorRole = normalizeRoleKey(String(req.user.roleCode || req.user.role || ''));
            const isSuperAdmin = actorRole === 'SUPER ADMIN' || actorRole === 'SUPER_ADMIN';
            if (!isSuperAdmin && !requiredRoles.includes(actorRole)) {
                return res.status(403).json({
                    success: false,
                    message: `Role ${req.user.roleCode || req.user.role} is not allowed to approve at ${request.currentStage}. Required: ${requiredRoles.join(', ')}`
                });
            }

            const metadata = { ...(request.metadata || {}) } as any;
            const jointApprovals = { ...(metadata.jointApprovals || {}) } as any;
            const zmRbm = { ...(jointApprovals.zmRbm || {}) } as any;

            if (zmRbm[actorRole]?.approvedByUserId) {
                return res.status(400).json({
                    success: false,
                    message: 'Your approval is already recorded at ZM/RBM stage. Waiting for the remaining approver(s).'
                });
            }

            zmRbm[actorRole] = {
                approvedByUserId: req.user.id,
                approvedBy: req.user.fullName,
                approvedAt: new Date().toISOString(),
                remarks: comments || ''
            };

            metadata.jointApprovals = {
                ...jointApprovals,
                zmRbm
            };

            const approvedRequiredRoles = requiredRoles.filter((role: string) => Boolean(zmRbm[role]?.approvedByUserId));
            const waitingFor = requiredRoles.filter((role: string) => !zmRbm[role]?.approvedByUserId);
            const approvalThresholdMet = approvedRequiredRoles.length >= Math.min(minApprovals, requiredRoles.length);

            if (!approvalThresholdMet) {
                await request.update({ metadata, updatedAt: new Date() });

                const userComment = String(comments ?? '').trim();
                const systemNote = `Joint approval recorded at ${request.currentStage} (waiting for remaining approver(s))`;
                const auditRemarks = userComment ? `${userComment} — ${systemNote}` : systemNote;

                await ConstitutionalAudit.create({
                    userId: req.user.id,
                    constitutionalChangeId: request.id,
                    action: AUDIT_ACTIONS.APPROVED,
                    remarks: auditRemarks,
                    details: {
                        stage: request.currentStage,
                        policy: {
                            stageCode,
                            approvalMode: policy?.approvalMode || 'ROLE_MANDATORY',
                            minApprovals,
                            requiredRoles
                        },
                        approvals: metadata.jointApprovals?.zmRbm || {},
                        waitingFor
                    }
                });

                try {
                    // Notify remaining approvers (co-approvers)
                    const remainingParticipants = await RequestParticipant.findAll({
                        where: {
                            requestId: request.id,
                            requestType: REQUEST_TYPES.CONSTITUTIONAL
                        },
                        include: [{ 
                            model: User, 
                            as: 'user', 
                            attributes: ['id', 'email', 'fullName', 'roleCode', 'mobileNumber'] 
                        }]
                    });

                    const portalBase = getFrontendBaseUrl();
                    
                    for (const p of remainingParticipants) {
                        const u = (p as any).user;
                        if (!u || u.id === req.user.id) continue;
                        
                        const uRole = normalizeRoleKey(u.roleCode || '');
                        if (waitingFor.includes(uRole)) {
                            const phone = u.mobileNumber || u.phone || null;
                            await NotificationService.notify(u.id, u.email, {
                                title: `Action Required: Co-approval for ${request.requestId}`,
                                message: `${req.user.fullName} has approved ${request.requestId}. Your approval is still required to move to the next stage.`,
                                channels: phone ? ['system', 'email', 'whatsapp'] : ['system', 'email'],
                                templateCode: 'WORKFLOW_ACTION_REQUIRED',
                                placeholders: {
                                    dealerName: request.dealerId, // We should ideally have dealer name, but request object has ID
                                    requestId: request.requestId,
                                    link: `${portalBase}/constitutional-change/${request.id}`,
                                    targetStage: request.currentStage,
                                    phone: phone || ''
                                }
                            });
                        }
                    }
                } catch (notifyErr) {
                    console.error('[constitutional] co-approver notification failed:', notifyErr);
                }

                return res.json({
                    success: true,
                    message: `Approval captured. Waiting for ${waitingFor.join(', ')} to approve.`
                });
            }

            await request.update({ metadata, updatedAt: new Date() });

            // Ensure the final joint approver also gets an explicit audit/worknote trail
            try {
                const finalUserComment = String(comments ?? '').trim();
                const finalBase = `[Joint Approval] ${req.user.fullName} provided the final required approval for the ${request.currentStage} stage.`;
                await writeWorkflowActivityWorknote({
                    requestId: request.id,
                    requestType: 'constitutional',
                    userId: req.user.id,
                    noteText: finalUserComment ? `${finalBase} Remarks: ${finalUserComment}` : finalBase,
                    noteType: 'internal'
                });
            } catch (wnErr) {
                console.error('[constitutional] final joint worknote:', wnErr);
            }
        }


        const nextStage = STAGE_FLOW_FORWARD[request.currentStage];
        if (!nextStage) {
            return res.status(400).json({ success: false, message: 'Cannot move forward from current stage' });
        }

        await ConstitutionalWorkflowService.transitionRequest(request, nextStage, req.user.id, {
            action: `Approved to ${nextStage}`,
            remarks: comments,
            userFullName: req.user.fullName
        });

        res.json({ success: true, message: actionSuccessMessage(OFFBOARDING_ACTIONS.APPROVE) });
    } catch (error) {
        console.error('Take action error:', error);
        res.status(500).json({ success: false, message: 'Error processing action' });
    }
};

/**
 * Returns document checklist based on target constitution
 */
export const getChecklist = async (req: AuthRequest, res: Response) => {
    try {
        const { targetConstitution } = req.query;
        if (!targetConstitution) return res.status(400).json({ success: false, message: 'targetConstitution is required' });

        const checklist = ConstitutionalWorkflowService.getDocumentChecklist(targetConstitution as string);
        res.json({ success: true, checklist });
    } catch (error) {
        res.status(500).json({ success: false, message: 'Error fetching checklist' });
    }
};

export const uploadDocuments = async (req: AuthRequest, res: Response) => {
    try {
        if (!req.user) return res.status(401).json({ success: false, message: 'Unauthorized' });

        const { id } = req.params;
        const { documents } = req.body;
        const resolvedId = await resolveConstitutionalUuid(String(id));

        const request = await ConstitutionalChange.findOne({
            where: { id: resolvedId }
        });

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

        const normalizedDocs = ensureDocumentIds(documents);

        await request.update({
            documents: normalizedDocs,
            updatedAt: new Date()
        });

        const docCount = normalizedDocs.length;
        await ConstitutionalAudit.create({
            userId: req.user.id,
            constitutionalChangeId: request.id,
            action: AUDIT_ACTIONS.DOCUMENT_UPLOADED,
            remarks: `Document checklist updated (${docCount} entr${docCount === 1 ? 'y' : 'ies'})`,
            details: { documentCount: docCount, stage: request.currentStage }
        });

        res.json({ success: true, message: 'Documents uploaded successfully', documents: normalizedDocs });
    } catch (error) {
        console.error('Upload documents error:', error);
        res.status(500).json({ success: false, message: 'Error uploading documents' });
    }
};

const applyConstitutionalDocumentDecision = async (
    req: AuthRequest,
    res: Response,
    targetStatus: 'Verified' | 'Rejected'
) => {
    try {
        const { id, documentId } = req.params;
        const { remarks } = req.body || {};

        if (!req.user) return res.status(401).json({ success: false, message: 'Unauthorized' });

        const roleCode = req.user.roleCode || req.user.role || '';
        if (!CONSTITUTIONAL_DOC_REVIEW_ROLES.has(roleCode)) {
            return res.status(403).json({ success: false, message: 'Forbidden: insufficient role to review documents' });
        }
        if (targetStatus === 'Rejected' && !String(remarks || '').trim()) {
            return res.status(400).json({ success: false, message: 'Remarks are required when rejecting a document' });
        }

        const resolvedId = await resolveConstitutionalUuid(String(id));
        const request = await ConstitutionalChange.findOne({ where: { id: resolvedId } });
        if (!request) {
            return res.status(404).json({ success: false, message: 'Request not found' });
        }

        let currentDocuments = ensureDocumentIds(request.documents || []);
        const documentIndex = findConstitutionalDocumentIndex(currentDocuments, String(documentId));
        if (documentIndex === -1) {
            return res.status(404).json({ success: false, message: 'Document not found' });
        }

        const docName =
            currentDocuments[documentIndex].name ||
            currentDocuments[documentIndex].fileName ||
            `Document ${currentDocuments[documentIndex].docNumber ?? documentIndex}`;

        currentDocuments[documentIndex] = {
            ...currentDocuments[documentIndex],
            status: targetStatus,
            verifiedOn: new Date().toISOString(),
            verifiedBy: req.user.fullName || 'System',
            ...(targetStatus === 'Rejected'
                ? {
                      rejectedOn: new Date().toISOString(),
                      rejectedBy: req.user.fullName || 'System',
                      rejectionReason: String(remarks).trim(),
                      rejectionRemarks: String(remarks).trim()
                  }
                : {})
        };

        const actionText = targetStatus === 'Verified' ? 'Document Verified' : 'Document Rejected';
        const updatedTimeline = [
            ...(request.timeline || []),
            {
                stage: request.currentStage,
                timestamp: new Date(),
                user: req.user.fullName || 'System',
                action: actionText,
                remarks:
                    targetStatus === 'Verified'
                        ? `Verified document: ${docName}`
                        : `Rejected document: ${docName}. ${String(remarks).trim()}`
            }
        ];

        await request.update({
            documents: currentDocuments,
            timeline: updatedTimeline,
            updatedAt: new Date()
        });

        await ConstitutionalAudit.create({
            userId: req.user.id,
            constitutionalChangeId: request.id,
            action:
                targetStatus === 'Verified' ? AUDIT_ACTIONS.DOCUMENT_VERIFIED : AUDIT_ACTIONS.DOCUMENT_REJECTED,
            remarks:
                targetStatus === 'Verified'
                    ? `Verified: ${docName}`
                    : `Rejected: ${docName}. ${String(remarks).trim()}`,
            details: {
                stage: request.currentStage,
                documentId: currentDocuments[documentIndex].id,
                documentName: docName,
                status: targetStatus
            }
        });

        return res.json({
            success: true,
            message:
                targetStatus === 'Verified' ? 'Document verified successfully' : 'Document rejected successfully',
            document: currentDocuments[documentIndex]
        });
    } catch (error) {
        console.error('Constitutional document decision error:', error);
        return res.status(500).json({ success: false, message: 'Error processing document decision' });
    }
};

export const verifyDocument = async (req: AuthRequest, res: Response) =>
    applyConstitutionalDocumentDecision(req, res, 'Verified');

export const rejectDocument = async (req: AuthRequest, res: Response) =>
    applyConstitutionalDocumentDecision(req, res, 'Rejected');
