import { Response } from 'express';
import db from '../../database/models/index.js';
import { RelocationWorkflowService } from '../../services/RelocationWorkflowService.js';
import { NomenclatureService } from '../../common/utils/nomenclature.js';
const { RelocationRequest, Outlet, User, Worknote, District, Region, Zone, RelocationDocument, AuditLog, Dealer } = db;
import { AUDIT_ACTIONS, ROLES, RELOCATION_STAGES, OUTLET_STATUS, 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 { Op, Transaction } from 'sequelize';
import { v4 as uuidv4 } from 'uuid';
import { AuthRequest } from '../../types/express.types.js';
import { formatDateTime } from '../../common/utils/dateUtils.js';
import { resolveEntityUuidByType } from '../../common/utils/requestResolver.js';
import { notifyRelocationSubmittedEmails } from '../../common/utils/workflow-email-notifications.js';
import { ParticipantService } from '../../services/ParticipantService.js';

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

/**
 * SRS §12.2.8 — persist approved new location onto the outlet master before the request is marked Completed.
 * Writes relocation_audit_logs, audit_logs (outlet entity), and a workflow worknote.
 */
async function syncOutletFromRelocationCompletion(relocation: any, actingUserId: string): Promise<void> {
    const outlet = await Outlet.findByPk(relocation.outletId);
    if (!outlet) {
        throw new Error(`Outlet not found for relocation ${relocation.requestId}`);
    }

    const oldData = {
        address: outlet.address,
        city: outlet.city,
        state: outlet.state,
        pincode: outlet.pincode,
        districtId: String(outlet.districtId)
    };

    const patch: Record<string, unknown> = {
        address: relocation.newAddress,
        city: relocation.newCity,
        state: relocation.newState
    };
    if (relocation.newDistrictId) {
        patch.districtId = relocation.newDistrictId;
    }

    await outlet.update(patch as any);
    await outlet.reload();

    const newData = {
        address: outlet.address,
        city: outlet.city,
        state: outlet.state,
        pincode: outlet.pincode,
        districtId: String(outlet.districtId)
    };

    await db.RelocationAudit.create({
        userId: actingUserId,
        relocationRequestId: relocation.id,
        action: 'Outlet location updated (relocation completed)',
        remarks: `Outlet ${outlet.code} master address synced from approved relocation ${relocation.requestId}.`,
        details: {
            relocationRequestId: relocation.requestId,
            outletId: outlet.id,
            outletCode: outlet.code,
            previousLocation: oldData,
            updatedLocation: newData
        }
    });

    await AuditLog.create({
        userId: actingUserId,
        action: AUDIT_ACTIONS.UPDATED,
        entityType: 'outlet',
        entityId: outlet.id,
        oldData,
        newData,
        ipAddress: null,
        userAgent: null
    } as any);

    await writeWorkflowActivityWorknote({
        requestId: relocation.id,
        requestType: 'relocation',
        userId: actingUserId,
        noteText:
            `Final relocation approval: outlet ${outlet.code} master location updated from ${relocation.requestId}. ` +
            `Before: ${oldData.address}, ${oldData.city}, ${oldData.state}. ` +
            `After: ${newData.address}, ${newData.city}, ${newData.state}.`,
        noteType: 'workflow'
    });
}

/**
 * Helper to assign evaluators for relocation requests based on outlet location hierarchy
 * Similar to assignStageEvaluators in onboarding
 */
const assignRelocationEvaluators = async (requestId: string, outletId: string) => {
    try {
        console.log(`[debug] Starting relocation evaluator assignment for Request: ${requestId}`);
        
        // Get outlet with full location hierarchy
        const outlet = await Outlet.findByPk(outletId, {
            include: [
                {
                    model: District,
                    as: 'district',
                    include: [
                        { model: Region, as: 'region' },
                        { model: Zone, as: 'zone' }
                    ]
                }
            ]
        });

        if (!outlet) {
            console.log(`[debug] Outlet ${outletId} not found`);
            return;
        }

        if (!outlet.district) {
            console.log(`[debug] Outlet ${outletId} has NO district linked. Skipping auto-assign.`);
            return;
        }

        const district = outlet.district;
        const region = district.region;
        const zone = district.zone;

        console.log(`[debug] Mapping for District: ${district.name}, Region: ${region?.name}, Zone: ${zone?.name}`);

        const evaluators: { id: string; role: string; stage: string }[] = [];

        // Stage 1: DD ASM (from district)
        if (district.asmId) {
            evaluators.push({ id: district.asmId, role: 'ASM', stage: RELOCATION_STAGES.ASM_REVIEW });
        }

        // Stage 2: RBM (from region)
        if (region && region.rbmId) {
            evaluators.push({ id: region.rbmId, role: 'RBM', stage: RELOCATION_STAGES.RBM_REVIEW });
        }

        // Stage 3: DD ZM (from district)
        if (district.zmId) {
            evaluators.push({ id: district.zmId, role: 'DD-ZM', stage: RELOCATION_STAGES.DD_ZM_REVIEW });
        }

        // Stage 4: ZBH (from zone)
        if (zone && zone.zbhId) {
            evaluators.push({ id: zone.zbhId, role: 'ZBH', stage: RELOCATION_STAGES.ZBH_REVIEW });
        }

        // Stage 5: DD Lead (National Singleton)
        const ddLead = await User.findOne({
            where: { roleCode: 'DD Lead', status: 'active' },
            include: [{
                model: db.UserRole,
                as: 'userRoles',
                where: { isActive: true } // Removed zoneId filter
            }]
        });
        if (ddLead) {
            evaluators.push({ id: ddLead.id, role: 'DD Lead', stage: RELOCATION_STAGES.DD_LEAD_REVIEW });
        }

        // Stage 6: NBH Approval (national)
        const nbh = await User.findOne({ where: { roleCode: 'NBH', status: 'active' } });
        if (nbh) {
            evaluators.push({ id: nbh.id, role: 'NBH', stage: RELOCATION_STAGES.NBH_APPROVAL });
        }

        // Stage 7: Legal Clearance (national)
        const legal = await User.findOne({ where: { roleCode: 'Legal Admin', status: 'active' } });
        if (legal) {
            evaluators.push({ id: legal.id, role: 'Legal', stage: RELOCATION_STAGES.LEGAL_CLEARANCE });
        }

        console.log(`[debug] Found ${evaluators.length} evaluators for relocation request`);

        const evaluatorInfo = evaluators.map(e => ({
            userId: e.id,
            role: e.role,
            stage: e.stage
        }));
        
        console.log(`[debug] Evaluators assigned:`, evaluatorInfo);
        return evaluatorInfo;
    } catch (error) {
        console.error('[debug] Error assigning relocation evaluators:', error);
    }
};

const REQUIRED_RELOCATION_DOCUMENTS = [
    'Property documents for new location',
    'Lease/Rental agreement for new location',
    'NOC from current landlord',
    'Municipal approvals',
    'Fire safety certificate',
    'Pollution clearance',
    'Layout/Floor plan of new location',
    'Photos of new location',
    'Locality map',
    'Building plan approval',
    'Electricity connection documents',
    'Water supply documents'
];

const isDocumentMappedToRequirement = (doc: any, requirement: string) => {
    if (!doc || !requirement) return false;
    const docType = String(doc.type || '').trim().toLowerCase();
    const docName = String(doc.name || '').trim().toLowerCase();
    const req = requirement.toLowerCase();
    if (docType === req) return true;
    // Keep matching behavior aligned with current frontend checklist logic
    const reqFirstToken = req.split(' ')[0];
    return Boolean(reqFirstToken) && (docType.includes(reqFirstToken) || docName.includes(reqFirstToken));
};

const getRelocationDocumentReadiness = (documents: any[]) => {
    const docs = Array.isArray(documents) ? documents : [];
    const missingUploads: string[] = [];
    const pendingVerification: string[] = [];

    for (const requirement of REQUIRED_RELOCATION_DOCUMENTS) {
        const matchedDocs = docs.filter((d: any) => isDocumentMappedToRequirement(d, requirement));
        if (!matchedDocs.length) {
            missingUploads.push(requirement);
            continue;
        }
        const hasVerified = matchedDocs.some((d: any) => String(d.status || '').toLowerCase() === 'verified');
        if (!hasVerified) {
            pendingVerification.push(requirement);
        }
    }

    return {
        totalRequired: REQUIRED_RELOCATION_DOCUMENTS.length,
        uploadedCount: REQUIRED_RELOCATION_DOCUMENTS.length - missingUploads.length,
        verifiedCount: REQUIRED_RELOCATION_DOCUMENTS.length - pendingVerification.length - missingUploads.length,
        missingUploads,
        pendingVerification
    };
};

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

        // Accept both 'proposed*' and 'new*' field naming conventions
        const {
            outletId, relocationType, currentAddress, currentCity, currentState,
            currentLatitude, currentLongitude, proposedAddress, proposedCity,
            proposedState, proposedLatitude, proposedLongitude, reason, proposedDate,
            // Frontend may send 'new*' fields directly
            newAddress, newCity, newState,
            // IDs for traceability
            newDistrictId, newStateId,
            distance,
            propertyType,
            // Fallback for coordinates if sent as non-proposed
            newLatitude, newLongitude
        } = req.body;

        // Use proposed* fields if available, otherwise fall back to new* fields
        const finalAddress = proposedAddress || newAddress;
        const finalCity = proposedCity || newCity;
        const finalState = proposedState || newState;
        const finalRelocationType = relocationType || 'Intercity';

        if (!outletId) {
            return res.status(400).json({ success: false, message: 'Outlet is required' });
        }

        const outlet = await Outlet.findByPk(outletId, { attributes: ['id', 'dealerId', 'status', 'name', 'code'] });
        if (!outlet) {
            return res.status(404).json({ success: false, message: 'Outlet not found' });
        }

        // SRS §12.2.8 — only active, eligible outlets
        if (outlet.status !== OUTLET_STATUS.ACTIVE) {
            return res.status(403).json({
                success: false,
                message: `Relocation can only be requested for active outlets. Current outlet status: ${outlet.status}`
            });
        }
        const subjectDealerUser = await User.findByPk(outlet.dealerId, {
            attributes: ['id', 'status', 'dealerId']
        });
        const subjectDealerProfile = subjectDealerUser?.dealerId
            ? await db.Dealer.findByPk(subjectDealerUser.dealerId, { attributes: ['id', 'status', 'onboardedAt'] })
            : null;
        const isDealerActive = String(subjectDealerUser?.status || '').toLowerCase() === 'active';
        const dealerProfileStatus = String(subjectDealerProfile?.status || '').toLowerCase();
        const isDealerProfileEligible = dealerProfileStatus === '' || dealerProfileStatus === 'active';
        if (!isDealerActive || !subjectDealerProfile?.onboardedAt || !isDealerProfileEligible) {
            return res.status(403).json({
                success: false,
                message: 'Relocation can be initiated only for active onboarded dealers.'
            });
        }

        const roleCode = req.user.roleCode as string;
        if (roleCode === ROLES.DEALER && String(outlet.dealerId) !== String(req.user.id)) {
            return res.status(403).json({
                success: false,
                message: 'You can only submit a relocation request for outlets assigned to your dealership account.'
            });
        }
        if (roleCode !== ROLES.DEALER && roleCode !== ROLES.SUPER_ADMIN) {
            return res.status(403).json({
                success: false,
                message: 'Only a dealer may initiate a relocation request (or Super Admin for support).'
            });
        }

        // SRS §12.2.7 — prevent parallel / duplicate open relocations for the same outlet
        const openExisting = await RelocationRequest.findOne({
            where: {
                outletId,
                status: { [Op.notIn]: ['Completed', 'Rejected', 'Revoked'] }
            },
            attributes: ['id', 'requestId', 'status', 'currentStage']
        });
        if (openExisting) {
            return res.status(409).json({
                success: false,
                message:
                    'An active relocation request already exists for this outlet. Complete or reject it before submitting a new one.',
                existingRequestId: openExisting.requestId
            });
        }

        const requestId = await NomenclatureService.generateRelocationId();

        const request = await RelocationRequest.create({
            requestId,
            outletId,
            dealerId: outlet.dealerId,
            relocationType: finalRelocationType,
            newAddress: finalAddress,
            newCity: finalCity,
            newState: finalState,
            newDistrictId: newDistrictId || null,
            newStateId: newStateId || null,
            reason,
            distance,
            propertyType: propertyType || null,
            expectedRelocationDate: proposedDate || null,
            currentLatitude: currentLatitude || null,
            currentLongitude: currentLongitude || null,
            newLatitude: proposedLatitude || newLatitude || null,
            newLongitude: proposedLongitude || newLongitude || null,
            currentStage: RELOCATION_STAGES.ASM_REVIEW,
            status: 'Pending ASM Review',
            progressPercentage: 10,
            documents: [],
            timeline: [{
                stage: 'Submitted',
                timestamp: new Date(),
                user: req.user.fullName,
                action: 'Request submitted'
            }]
        });

        // Auto-assign participants using ParticipantService
        await ParticipantService.assignRelocationParticipants(request.id);

        await db.RelocationAudit.create({
            userId: req.user.id,
            relocationRequestId: request.id,
            action: AUDIT_ACTIONS.CREATED,
            remarks: 'Relocation request submitted',
            details: {
                requestId: request.requestId,
                stage: RELOCATION_STAGES.ASM_REVIEW,
                status: request.status
            }
        });

        await notifyRelocationSubmittedEmails(request.toJSON ? request.toJSON() : request, {
            email: req.user.email || '',
            fullName: req.user.fullName
        }).catch((e) => console.error('[relocation.submitRequest] notify emails:', e));

        res.status(201).json({
            success: true,
            message: 'Relocation request submitted successfully',
            requestId: request.requestId
        });
    } catch (error) {
        console.error('Submit relocation 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 RelocationRequest.findAndCountAll({
            where,
            include: [
                {
                    model: Outlet,
                    as: 'outlet',
                    attributes: ['code', 'name', 'address', 'city', 'state', 'pincode'],
                    include: [
                        {
                            model: District,
                            as: 'district',
                            attributes: ['id', 'name', 'asmId', 'zmId'],
                            include: [
                                { model: Region, as: 'region', attributes: ['id', 'name', 'rbmId'] },
                                { model: Zone, as: 'zone', attributes: ['id', 'name', 'zbhId'] }
                            ]
                        }
                    ]
                },
                {
                    model: User,
                    as: 'dealer',
                    attributes: ['fullName'],
                    include: [
                        { model: Dealer, as: 'dealerProfile', attributes: ['asmId'] }
                    ]
                }
            ],
            order: [['createdAt', 'DESC']],
            limit,
            offset,
            distinct: true
        });

        // Filter requests based on user's role and location assignments
        const filteredRequests = requests.filter((request: any) => {
            // Dealers see only their own requests
            if (req.user?.roleCode === 'Dealer') {
                return request.dealerId === req.user.id;
            }
            
            // For internal users, check if they are assigned to this outlet's location
            const outlet = request.outlet;
            if (!outlet?.district) return false;
            
            const district = outlet.district;
            const region = district.region;
            const zone = district.zone;
            const userId = req.user?.id;
            const userRoleCode = req.user?.roleCode;

            // National roles see all requests
            const nationalRoles = [
                ROLES.NBH, 
                ROLES.DD_LEAD, 
                ROLES.DD_HEAD, 
                ROLES.LEGAL_ADMIN, 
                ROLES.SUPER_ADMIN, 
                ROLES.DD_ADMIN,
                ROLES.FINANCE,
                ROLES.CCO,
                ROLES.CEO
            ];
            if (userRoleCode && (nationalRoles as readonly string[]).includes(userRoleCode)) {
                return true;
            }

            // ASM is tied to the dealer/outlet (Dealer.asmId); district.asmId kept as fallback for legacy data.
            const outletAsmId = (request as any).dealer?.dealerProfile?.asmId as string | undefined;
            const isAssigned =
                outletAsmId === userId ||
                district.asmId === userId ||
                region?.rbmId === userId ||
                district.zmId === userId ||
                zone?.zbhId === userId;

            return isAssigned;
        });
        
        // Enrich responses with currentLocation and proposedLocation for frontend
        const enrichedRequests = filteredRequests.map((request: any) => {
            const reqData = request.get({ plain: true });
            const outlet = reqData.outlet;
            
            return {
                ...reqData,
                currentLocation: outlet ? `${outlet.address}, ${outlet.city}, ${outlet.state} - ${outlet.pincode}` : 'N/A',
                proposedLocation: `${reqData.newAddress}, ${reqData.newCity}, ${reqData.newState}`
            };
        });

        res.json({ 
            success: true, 
            requests: enrichedRequests,
            meta: {
                total: count,
                totalPages: Math.ceil(count / limit),
                currentPage: page,
                limit,
                stats: {
                    total: count,
                    pending: await RelocationRequest.count({ where: { ...where, status: { [Op.notIn]: ['Completed', 'Closed', 'Rejected', 'Revoked'] } } }),
                    completed: await RelocationRequest.count({ where: { ...where, status: { [Op.in]: ['Completed', 'Closed'] } } }),
                    rejected: await RelocationRequest.count({ where: { ...where, status: { [Op.in]: ['Rejected', 'Revoked'] } } })
                }
            }
        });
    } catch (error) {
        console.error('Get relocation requests 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.id as string;
        const resolvedId = await resolveRelocationUuid(id);
        
        const request = await RelocationRequest.findOne({
            where: { id: resolvedId },
            include: [
                {
                    model: Outlet,
                    as: 'outlet',
                    include: [
                        {
                            model: District,
                            as: 'district',
                            attributes: ['id', 'name', 'asmId', 'zmId'],
                            include: [
                                { model: Region, as: 'region', attributes: ['id', 'name', 'rbmId'] },
                                { model: Zone, as: 'zone', attributes: ['id', 'name', 'zbhId'] }
                            ]
                        }
                    ]
                },
                {
                    model: User,
                    as: 'dealer',
                    attributes: ['fullName', 'email'],
                    include: [
                        { model: Dealer, as: 'dealerProfile', attributes: ['asmId'] }
                    ]
                },
                {
                    model: Worknote,
                    as: 'worknotes',
                    order: [['createdAt', 'ASC']]
                }
            ]
        });

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

        // Compute participants dynamically based on outlet location hierarchy
        const participants: any[] = [];
        const outlet = (request as any).outlet;
        if (outlet?.district) {
            const district = outlet.district;
            const region = district.region;
            const zone = district.zone;

            const outletLevelAsmId = (request as any).dealer?.dealerProfile?.asmId ?? null;
            const asmReviewerId = outletLevelAsmId || district.asmId;
            const evaluatorRoles: any[] = [
                { id: asmReviewerId, roleCode: 'ASM', stage: RELOCATION_STAGES.ASM_REVIEW },
                { id: region?.rbmId, roleCode: 'RBM', stage: RELOCATION_STAGES.RBM_REVIEW },
                { id: district.zmId, roleCode: 'DD-ZM', stage: RELOCATION_STAGES.DD_ZM_REVIEW },
                { id: zone?.zbhId, roleCode: 'ZBH', stage: RELOCATION_STAGES.ZBH_REVIEW }
            ];

            // Get DD Lead (National Singleton)
            const ddLead = await User.findOne({
                where: { roleCode: 'DD Lead', status: 'active' },
                include: [{
                    model: db.UserRole,
                    as: 'userRoles',
                    where: { isActive: true } // Removed zoneId filter
                }]
            });
            if (ddLead) evaluatorRoles.push({ id: ddLead.id, roleCode: 'DD Lead', stage: RELOCATION_STAGES.DD_LEAD_REVIEW });

            // Get NBH (national) - Approval Stage
            const nbh = await User.findOne({ where: { roleCode: 'NBH', status: 'active' } });
            if (nbh) {
                evaluatorRoles.push({ id: nbh.id, roleCode: 'NBH', stage: RELOCATION_STAGES.NBH_APPROVAL });
            }

            // Get Legal (national)
            const legal = await User.findOne({ where: { roleCode: 'Legal Admin', status: 'active' } });
            if (legal) evaluatorRoles.push({ id: legal.id, roleCode: 'Legal Admin', stage: RELOCATION_STAGES.LEGAL_CLEARANCE });

            // Fetch user details for each evaluator
            for (const evaluator of evaluatorRoles) {
                if (evaluator.id) {
                    const user = await User.findByPk(evaluator.id, { attributes: ['id', 'fullName', 'email', 'roleCode'] });
                    if (user) {
                        participants.push({
                            id: `eval-${evaluator.stage}`,
                            userId: evaluator.id,
                            participantType: 'reviewer',
                            metadata: { stage: evaluator.stage, role: evaluator.roleCode, autoAssigned: true },
                            user
                        });
                    }
                }
            }
        }

        // Enrich response with currentLocation and proposedLocation
        const response = request.toJSON();
        (response as any).currentLocation = (response as any).outlet ? `${(response as any).outlet.address}, ${(response as any).outlet.city}, ${(response as any).outlet.state} - ${(response as any).outlet.pincode}` : 'N/A';
        (response as any).proposedLocation = `${(response as any).newAddress}, ${(response as any).newCity}, ${(response as any).newState}`;
        (response as any).participants = participants;

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

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

        const { id } = req.params;
        const { action, comments, remarks } = req.body;
        const reviewComments = (comments ?? remarks ?? '') as string;

        // Normalize to lowercase alphanumeric for robust comparison (handles "Send Back", "sendBack", "send-back")
        const normalizedActionFull = String(action || '').trim().toLowerCase().replace(/[^a-z0-9]/g, '');
        
        // Map back to uppercase underscore format for internal logic if needed, 
        // but it's safer to compare against constants directly.
        const normalizedAction = normalizedActionFull === 'approve' ? 'APPROVE' :
                                normalizedActionFull === 'reject' ? 'REJECT' :
                                normalizedActionFull === 'sendback' ? 'SEND_BACK' :
                                normalizedActionFull === 'revoke' ? 'REVOKE' :
                                normalizedActionFull === 'hold' ? 'HOLD' : normalizedActionFull.toUpperCase();

        const resolvedId = await resolveRelocationUuid(String(id));

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

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

        if (request.status === 'Completed' || request.currentStage === RELOCATION_STAGES.COMPLETED) {
            return res.status(400).json({ success: false, message: 'This relocation request is already completed.' });
        }
        if (request.status === 'Rejected' || request.currentStage === RELOCATION_STAGES.REJECTED) {
            return res.status(400).json({ success: false, message: 'This relocation request is already rejected.' });
        }
        if (request.status === 'Revoked') {
            return res.status(400).json({ success: false, message: 'This relocation request has been revoked.' });
        }

        const supportedActions = ['APPROVE', 'REJECT', 'SEND_BACK', 'REVOKE', 'HOLD'];
        if (!supportedActions.includes(normalizedAction)) {
            return res.status(400).json({ success: false, message: `Unsupported action: ${action}` });
        }
        if (normalizedAction === 'HOLD') {
            return res.status(501).json({ success: false, message: 'Hold is not implemented for relocation yet.' });
        }

        // SRS §12.2.8 — Send Back / Revoke communicated through Work Notes with mandatory remarks
        if (normalizedAction === 'SEND_BACK' || normalizedAction === 'REVOKE') {
            const validation = validateOffboardingAction(
                normalizedAction === 'SEND_BACK' ? OFFBOARDING_ACTIONS.SEND_BACK : OFFBOARDING_ACTIONS.REVOKE,
                reviewComments
            );
            if (!validation.valid) {
                return res.status(400).json({ success: false, message: validation.message });
            }
        }

        // 1. Authorization Check via Workflow Service
        const canAction = await RelocationWorkflowService.canUserAction(request, req.user);
        if (!canAction) {
            return res.status(403).json({
                success: false,
                message: `Forbidden: Your role (${req.user?.roleCode}) is not authorized to take actions for the current stage: ${request.currentStage}`
            });
        }

        let newStatus = request.status;
        let newCurrentStage = request.currentStage;

        const stageFlow: Record<string, string> = {
            [RELOCATION_STAGES.ASM_REVIEW]: RELOCATION_STAGES.RBM_REVIEW,
            'DD Admin Review': RELOCATION_STAGES.RBM_REVIEW, // Legacy support
            [RELOCATION_STAGES.RBM_REVIEW]: RELOCATION_STAGES.DD_ZM_REVIEW,
            [RELOCATION_STAGES.DD_ZM_REVIEW]: RELOCATION_STAGES.ZBH_REVIEW,
            [RELOCATION_STAGES.ZBH_REVIEW]: RELOCATION_STAGES.DD_LEAD_REVIEW,
            [RELOCATION_STAGES.DD_LEAD_REVIEW]: RELOCATION_STAGES.NBH_APPROVAL,
            [RELOCATION_STAGES.NBH_APPROVAL]: RELOCATION_STAGES.LEGAL_CLEARANCE,
            [RELOCATION_STAGES.LEGAL_CLEARANCE]: RELOCATION_STAGES.COMPLETED
        };

        const reverseStageFlow: Record<string, string> = {
            'DD Admin Review': RELOCATION_STAGES.ASM_REVIEW,
            [RELOCATION_STAGES.RBM_REVIEW]: RELOCATION_STAGES.ASM_REVIEW,
            [RELOCATION_STAGES.DD_ZM_REVIEW]: RELOCATION_STAGES.RBM_REVIEW,
            [RELOCATION_STAGES.ZBH_REVIEW]: RELOCATION_STAGES.DD_ZM_REVIEW,
            [RELOCATION_STAGES.DD_LEAD_REVIEW]: RELOCATION_STAGES.ZBH_REVIEW,
            [RELOCATION_STAGES.NBH_APPROVAL]: RELOCATION_STAGES.DD_LEAD_REVIEW,
            [RELOCATION_STAGES.LEGAL_CLEARANCE]: RELOCATION_STAGES.NBH_APPROVAL,
            [RELOCATION_STAGES.COMPLETED]: RELOCATION_STAGES.LEGAL_CLEARANCE
        };

        /** Canonical order for progress % (must stay aligned with stageFlow chain, excluding terminal). */
        const RELOCATION_PIPELINE_STAGES: string[] = [
            RELOCATION_STAGES.ASM_REVIEW,
            RELOCATION_STAGES.RBM_REVIEW,
            RELOCATION_STAGES.DD_ZM_REVIEW,
            RELOCATION_STAGES.ZBH_REVIEW,
            RELOCATION_STAGES.DD_LEAD_REVIEW,
            RELOCATION_STAGES.NBH_APPROVAL,
            RELOCATION_STAGES.LEGAL_CLEARANCE
        ];

        /** ~10% per stage while in flight; 100% only when Completed. Avoids showing 100% on NBH EOR before final approve. */
        const relocationProgressPercentageForStage = (stage: string): number => {
            if (stage === RELOCATION_STAGES.COMPLETED) return 100;
            const normalized = stage === 'DD Admin Review' ? RELOCATION_STAGES.ASM_REVIEW : stage;
            const i = RELOCATION_PIPELINE_STAGES.indexOf(normalized);
            if (i < 0) return 10;
            return Math.min(100, Math.round(((i + 1) / (RELOCATION_PIPELINE_STAGES.length + 1)) * 100));
        };

        let actionType: string = OFFBOARDING_ACTIONS.APPROVE;

        if (normalizedAction === 'APPROVE') {
            newCurrentStage = stageFlow[request.currentStage as string] || request.currentStage;
            newStatus = newCurrentStage === RELOCATION_STAGES.COMPLETED ? 'Completed' : `Pending ${newCurrentStage}`;
            actionType = OFFBOARDING_ACTIONS.APPROVE;
        } else if (normalizedAction === 'REJECT') {
            newStatus = 'Rejected';
            newCurrentStage = RELOCATION_STAGES.REJECTED;
            actionType = OFFBOARDING_ACTIONS.REJECT;
        } else if (normalizedAction === 'SEND_BACK') {
            const prevStage = reverseStageFlow[request.currentStage as string];
            if (!prevStage) {
                return res.status(400).json({
                    success: false,
                    message: 'Send Back is not available at this stage (already at the first review step).'
                });
            }
            newCurrentStage = prevStage;
            newStatus = `Pending ${prevStage}`;
        } else if (normalizedAction === 'REVOKE') {
            newStatus = 'Revoked';
            newCurrentStage = RELOCATION_STAGES.REJECTED;
        }

        // SRS §12.2.8 — document upload/verification is tracked for readiness display
        // (see getRelocationDocumentReadiness + progress calculation) but is NOT a hard
        // blocker on NBH Approval / Legal Clearance. Per business decision, these are
        // senior approval authorities who retain discretion to proceed even when the
        // readiness panel still shows missing uploads or pending verifications. The
        // readiness data continues to be surfaced on the UI so gaps remain visible.

        let newProgress = request.progressPercentage;
        if (normalizedAction === 'APPROVE') {
            newProgress = relocationProgressPercentageForStage(newCurrentStage as string);
        } else if (normalizedAction === 'SEND_BACK') {
            newProgress = relocationProgressPercentageForStage(newCurrentStage as string);
        } else if (normalizedAction === 'REJECT' || normalizedAction === 'REVOKE') {
            newProgress = 100;
        }

        // SRS §12.2.8 — update outlet master before marking relocation Completed (fails the action if sync fails)
        if (normalizedAction === 'APPROVE' && newCurrentStage === RELOCATION_STAGES.COMPLETED) {
            try {
                await syncOutletFromRelocationCompletion(request, req.user.id);
            } catch (syncErr: any) {
                console.error('[RelocationController] Outlet sync failed before relocation completion:', syncErr);
                return res.status(500).json({
                    success: false,
                    message:
                        syncErr?.message ||
                        'Could not update outlet master location before completing relocation. The request was not completed.'
                });
            }
        }

        await RelocationWorkflowService.transitionRelocation(request, newStatus, req.user?.id || null, {
            reason: reviewComments || 'No remarks provided',
            stage: newCurrentStage,
            action: normalizedAction,
            progressPercentage: newProgress
        });

        // 3. Work note: mandatory for Send Back / Revoke; optional for other actions when remarks provided
        const shouldWriteWorknote =
            Boolean(String(reviewComments).trim()) &&
            (normalizedAction === 'SEND_BACK' ||
                normalizedAction === 'REVOKE' ||
                normalizedAction === 'APPROVE' ||
                normalizedAction === 'REJECT');
        if (shouldWriteWorknote) {
            const prefix =
                normalizedAction === 'SEND_BACK'
                    ? '[Send Back] '
                    : normalizedAction === 'REVOKE'
                        ? '[Revoke] '
                        : '';
            await Worknote.create({
                requestId: request.id,
                requestType: 'relocation' as any,
                userId: req.user.id,
                noteText: `${prefix}${reviewComments}`,
                noteType: normalizedAction === 'SEND_BACK' || normalizedAction === 'REVOKE' ? 'workflow' : 'internal',
                status: 'active'
            });
        }

        const actionLabel =
            normalizedAction === 'SEND_BACK'
                ? 'sent back'
                : normalizedAction === 'REVOKE'
                    ? 'revoked'
                    : (normalizedAction || 'ACTION').toLowerCase();
        res.json({
            success: true,
            message: `Request ${actionLabel} successfully`
        });
    } catch (error) {
        console.error('Take action error:', error);
        res.status(500).json({ success: false, message: 'Error processing action' });
    }
};

export const uploadDocuments = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params;
        const { documentType, stage } = req.body;
        const file = req.file;

        if (!file) {
            return res.status(400).json({ success: false, message: 'No file uploaded' });
        }

        if (!documentType) {
            return res.status(400).json({ success: false, message: 'Document type is required' });
        }

        const resolvedId = await resolveRelocationUuid(String(id));

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

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

        // Create Document Record
        const newDoc = await RelocationDocument.create({
            relocationId: request.id,
            documentType,
            stage: stage || request.currentStage,
            fileName: file.originalname,
            filePath: file.path,
            fileSize: file.size,
            mimeType: file.mimetype,
            uploadedBy: req.user?.id,
            status: 'active'
        });

        // Update the documents JSON array in RelocationRequest for quick access/legacy compatibility
        const currentDocuments = request.documents || [];
        const updatedDocuments = [...currentDocuments, {
            id: newDoc.id,
            name: file.originalname,
            type: documentType,
            url: newDoc.filePath, // Critical for frontend preview/download
            mimeType: newDoc.mimeType, // Useful for previewer
            uploadedOn: new Date(),
            uploadedBy: req.user?.fullName || 'System',
            status: 'Pending Verification',
            category: 'Relocation'
        }];

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

        await db.RelocationAudit.create({
            userId: req.user?.id || null,
            relocationRequestId: request.id,
            action: AUDIT_ACTIONS.DOCUMENT_UPLOADED,
            remarks: `Uploaded: ${documentType}`,
            details: {
                stage: request.currentStage,
                documentType,
                fileName: file.originalname,
                documentId: newDoc.id
            }
        });

        try {
            const { ensureRelocationEorChecklistSeeded } = await import('../eor/eor.controller.js');
            await ensureRelocationEorChecklistSeeded(request.id);
        } catch (e) {
            console.error('[RelocationController] EOR checklist sync after upload:', e);
        }

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

const applyRelocationDocumentDecision = 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 resolvedId = await resolveRelocationUuid(String(id));

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

        const isInternal = req.user.roleCode !== 'Dealer';
        if (!isInternal) {
            return res.status(403).json({ success: false, message: 'Forbidden: Dealers cannot verify or reject documents' });
        }
        if (targetStatus === 'Rejected' && !String(remarks || '').trim()) {
            return res.status(400).json({ success: false, message: 'Remarks are required when rejecting a document' });
        }

        const docRecord = await RelocationDocument.findByPk(documentId);
        if (docRecord) {
            await docRecord.update({ status: targetStatus });
        }

        const currentDocuments = request.documents || [];
        const documentIndex = currentDocuments.findIndex((d: any) => d.id === documentId);
        if (documentIndex === -1) {
            return res.status(404).json({ success: false, message: 'Document not found in request tracker' });
        }

        currentDocuments[documentIndex].status = targetStatus;
        currentDocuments[documentIndex].verifiedBy = req.user.fullName;
        currentDocuments[documentIndex].verifiedOn = new Date();
        if (targetStatus === 'Rejected') {
            currentDocuments[documentIndex].rejectionRemarks = remarks || 'Rejected by reviewer';
        }

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

        const totalRequired = REQUIRED_RELOCATION_DOCUMENTS.length;
        const verifiedCount = currentDocuments.filter((d: any) => d.status === 'Verified').length;
        const progressPercentage = Math.min(Math.round((verifiedCount / totalRequired) * 100), 100);

        let newStatus = request.status;
        if (request.status === 'Pending') {
            newStatus = 'In Progress';
        }

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

        request.changed('documents', true);
        request.changed('timeline', true);
        await request.save();

        await db.RelocationAudit.create({
            userId: req.user?.id || null,
            relocationRequestId: request.id,
            action: targetStatus === 'Verified' ? AUDIT_ACTIONS.DOCUMENT_VERIFIED : AUDIT_ACTIONS.DOCUMENT_REJECTED,
            remarks:
                targetStatus === 'Verified'
                    ? `Verified: ${currentDocuments[documentIndex].name}`
                    : `Rejected: ${currentDocuments[documentIndex].name}. ${remarks || ''}`.trim(),
            details: {
                stage: request.currentStage,
                documentId,
                documentName: currentDocuments[documentIndex].name,
                status: targetStatus
            }
        });

        try {
            const { ensureRelocationEorChecklistSeeded } = await import('../eor/eor.controller.js');
            await ensureRelocationEorChecklistSeeded(request.id);
        } catch (e) {
            console.error('[RelocationController] EOR checklist sync after document decision:', e);
        }

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

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

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

// Helper function to calculate distance between two coordinates
function calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
    const R = 6371; // Radius of Earth in km
    const dLat = (lat2 - lat1) * Math.PI / 180;
    const dLon = (lon2 - lon1) * Math.PI / 180;
    const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
        Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
        Math.sin(dLon / 2) * Math.sin(dLon / 2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    return R * c;
}
