import db from '../database/models/index.js';
import { ROLES, REQUEST_TYPES } from '../common/config/constants.js';
import { Op } from 'sequelize';
import { isAutoAssignmentEnabled } from './AutoAssignmentConfigService.js';

const { 
    RequestParticipant, 
    User, 
    TerminationRequest, 
    ConstitutionalChange, 
    Dealer, 
    Application, 
    District, 
    Region, 
    Zone,
    Outlet,
    RelocationRequest
} = db;

export class ParticipantService {
    /**
     * Common helper to add a participant
     */
    private static async addParticipant(requestId: string, requestType: string, userId: string, participantType: string = 'contributor', metadata: any = {}) {
        try {
            const existing = await RequestParticipant.findOne({
                where: {
                    requestId,
                    requestType,
                    userId
                }
            });

            if (existing) {
                // Requirement: Auto-assignment logic skips participants with revokedAt to prevent re-adding
                if (existing.metadata?.revokedAt) {
                    console.log(`[ParticipantService] Skipping revoked participant ${userId} for ${requestType} ${requestId}`);
                    return;
                }
                return; // Already exists
            }

            await RequestParticipant.create({
                requestId,
                requestType,
                userId,
                participantType,
                joinedMethod: 'auto',
                metadata: {
                    ...metadata,
                    autoMapped: true,
                    assignedAt: new Date()
                }
            });
        } catch (error) {
            console.error(`Error adding participant ${userId} to ${requestType} ${requestId}:`, error);
        }
    }

    /**
     * Resolves location-based managers for a dealer
     */
    private static async getDealerLocationManagers(dealerId: string) {
        const dealer = await Dealer.findByPk(dealerId, {
            include: [{
                model: Application,
                as: 'application',
                include: [{
                    model: District,
                    as: 'district',
                    include: [
                        { model: Region, as: 'region' },
                        { model: Zone, as: 'zone' }
                    ]
                }]
            }]
        });

        if (!dealer || !dealer.application || !dealer.application.district) {
            return null;
        }

        const district = dealer.application.district;
        return {
            asmId: dealer.asmId || null,
            zmId: district.zmId,
            rbmId: district.region?.rbmId,
            zbhId: district.zone?.zbhId
        };
    }

    /**
     * Assign participants for Termination Request
     */
    static async assignTerminationParticipants(requestId: string) {
        try {
            if (!await isAutoAssignmentEnabled('TERMINATION')) {
                console.log(`[ParticipantService] Auto-assignment disabled for TERMINATION. Skipping for ${requestId}`);
                return;
            }
            const termination = await db.TerminationRequest.findByPk(requestId);
            if (!termination) {
                console.error(`[ParticipantService] Termination Request not found: ${requestId}`);
                return;
            }

            const participantIds = new Set<string>();
            
            // 0. The Dealer themselves (Affected Party) should be a participant
            if (termination.dealerId) {
                // In Termination, dealerId is likely the Dealer Profile ID, 
                // need to resolve to User ID for participants
                const dealerUser = await User.findOne({ 
                    where: { dealerId: termination.dealerId, roleCode: ROLES.DEALER } 
                });
                if (dealerUser) participantIds.add(dealerUser.id);
            }

            // 1. Location based managers
            if (termination.dealerId) {
                const managers = await this.getDealerLocationManagers(termination.dealerId);
                if (managers) {
                    if (managers.asmId) participantIds.add(managers.asmId);
                    if (managers.rbmId) participantIds.add(managers.rbmId);
                    if (managers.zbhId) participantIds.add(managers.zbhId);
                }
            }

            // 2. National roles
            const nationalRoles = [
                ROLES.DD_LEAD, 
                ROLES.NBH, 
                ROLES.CCO,
                ROLES.CEO,
                ROLES.DD_ADMIN, 
                ROLES.FINANCE, 
                ROLES.LEGAL_ADMIN,
                ROLES.SUPER_ADMIN
            ];
            
            const nationalUsers = await User.findAll({
                where: {
                    roleCode: { [Op.in]: nationalRoles },
                    status: 'active'
                },
                attributes: ['id']
            });

            nationalUsers.forEach((u: any) => participantIds.add(u.id));

            // 3. Add all unique participants
            let addedCount = 0;
            for (const userId of participantIds) {
                // Determine type (Dealer profile id is not userId)
                // We'll check if the userId matches the resolved dealer user
                const isDealer = termination.dealerId && (await User.findByPk(userId))?.dealerId === termination.dealerId;
                const pType = isDealer ? 'owner' : 'contributor';
                
                await this.addParticipant(termination.id, REQUEST_TYPES.TERMINATION, userId, pType);
                addedCount++;
            }

            console.log(`[ParticipantService] Added ${addedCount} participants to termination ${requestId}`);
        } catch (error) {
            console.error('Error assigning termination participants:', error);
        }
    }

    /**
     * Assign participants for Constitutional Change Request
     */
    static async assignConstitutionalParticipants(requestId: string) {
        try {
            if (!await isAutoAssignmentEnabled('CONSTITUTIONAL')) {
                console.log(`[ParticipantService] Auto-assignment disabled for CONSTITUTIONAL. Skipping for ${requestId}`);
                return;
            }
            const request = await ConstitutionalChange.findByPk(requestId);
            if (!request) return;

            const participantIds = new Set<string>();

            // 0. The Dealer (Requester) should be a participant
            if (request.dealerId) participantIds.add(request.dealerId);

            // In ConstitutionalChange model, dealerId is the User ID
            const user = await User.findByPk(request.dealerId);
            if (user && user.dealerId) {
                const managers = await this.getDealerLocationManagers(user.dealerId);
                // 1. Location based managers
                if (managers) {
                    if (managers.asmId) participantIds.add(managers.asmId);
                    if (managers.zmId) participantIds.add(managers.zmId);
                    if (managers.rbmId) participantIds.add(managers.rbmId);
                    if (managers.zbhId) participantIds.add(managers.zbhId);
                }
            }

            // 2. National roles
            const nationalRoles = [
                ROLES.DD_LEAD, 
                ROLES.DD_HEAD, 
                ROLES.NBH, 
                ROLES.CCO,
                ROLES.CEO,
                ROLES.FINANCE,
                ROLES.DD_ADMIN,
                ROLES.LEGAL_ADMIN, 
                ROLES.SUPER_ADMIN
            ];
            const nationalUsers = await User.findAll({
                where: {
                    roleCode: { [Op.in]: nationalRoles },
                    status: 'active'
                },
                attributes: ['id']
            });

            nationalUsers.forEach((u: any) => participantIds.add(u.id));

            // 3. Add all unique participants
            let addedCount = 0;
            for (const userId of participantIds) {
                await this.addParticipant(request.id, REQUEST_TYPES.CONSTITUTIONAL, userId);
                addedCount++;
            }

            console.log(`[ParticipantService] Added ${addedCount} participants to constitutional change ${requestId}`);
        } catch (error) {
            console.error('Error assigning constitutional participants:', error);
        }
    }

    /**
     * Assign participants for Resignation Request
     */
    static async assignResignationParticipants(requestId: string) {
        try {
            if (!await isAutoAssignmentEnabled('RESIGNATION')) {
                console.log(`[ParticipantService] Auto-assignment disabled for RESIGNATION. Skipping for ${requestId}`);
                return;
            }
            const resignation = await db.Resignation.findByPk(requestId);
            if (!resignation) {
                console.error(`[ParticipantService] Resignation not found: ${requestId}`);
                return;
            }

            const participantIds = new Set<string>();
            
            // 0. The Dealer themselves (Requester) should be a participant
            if (resignation.dealerId) {
                participantIds.add(resignation.dealerId);
            }

            // In Resignation model, dealerId is the User ID
            const user = await User.findByPk(resignation.dealerId);
            
            // 1. Try to get Location based managers if dealer profile exists
            if (user && user.dealerId) {
                const managers = await this.getDealerLocationManagers(user.dealerId);
                if (managers) {
                    if (managers.asmId) participantIds.add(managers.asmId);
                    if (managers.rbmId) participantIds.add(managers.rbmId);
                    if (managers.zbhId) participantIds.add(managers.zbhId);
                }
            } else {
                console.warn(`[ParticipantService] No Dealer Profile link found for user ${resignation.dealerId}. Only adding national roles.`);
            }

            // 2. National roles - Essential for workflow transparency
            const nationalRoles = [
                ROLES.DD_LEAD, 
                ROLES.DD_HEAD,
                ROLES.NBH, 
                ROLES.CCO,
                ROLES.CEO,
                ROLES.DD_ADMIN, 
                ROLES.FINANCE, 
                ROLES.LEGAL_ADMIN,
                ROLES.SUPER_ADMIN // Added Super Admin as observer
            ];
            
            const nationalUsers = await User.findAll({
                where: {
                    roleCode: { [Op.in]: nationalRoles },
                    status: 'active'
                },
                attributes: ['id']
            });

            nationalUsers.forEach((u: any) => participantIds.add(u.id));

            // 3. Add all unique participants
            let addedCount = 0;
            for (const userId of participantIds) {
                // Dealer gets 'owner' type, others get 'contributor'
                const pType = userId === resignation.dealerId ? 'owner' : 'contributor';
                await this.addParticipant(resignation.id, REQUEST_TYPES.RESIGNATION, userId, pType);
                addedCount++;
            }

            console.log(`[ParticipantService] Added ${addedCount} participants to resignation ${requestId}`);
        } catch (error) {
            console.error('Error assigning resignation participants:', error);
        }
    }

    /**
     * Assign participants for Relocation Request
     */
    static async assignRelocationParticipants(requestId: string) {
        try {
            if (!await isAutoAssignmentEnabled('RELOCATION')) {
                console.log(`[ParticipantService] Auto-assignment disabled for RELOCATION. Skipping for ${requestId}`);
                return;
            }
            const relocation = await db.RelocationRequest.findByPk(requestId, {
                include: [{
                    model: Outlet,
                    as: 'outlet',
                    include: [{
                        model: District,
                        as: 'district',
                        include: [
                            { model: Region, as: 'region' },
                            { model: Zone, as: 'zone' }
                        ]
                    }]
                }]
            });

            if (!relocation) {
                console.error(`[ParticipantService] Relocation not found: ${requestId}`);
                return;
            }

            const participantIds = new Set<string>();

            // 0. The Dealer (Requester)
            if (relocation.dealerId) {
                participantIds.add(relocation.dealerId);
            }

            // 1. Location-based managers from Outlet
            const outlet = (relocation as any).outlet;
            if (outlet && outlet.district) {
                const district = outlet.district;
                // Canonical ASM for relocation visibility/actions is district.asmId (see relocation.controller getRequests / evaluators).
                // Dealer.asmId may differ or be unset while district.asmId is set.
                if (district.asmId) participantIds.add(district.asmId);
                if (relocation.dealerId) {
                    const dealerUser = await User.findByPk(relocation.dealerId, { attributes: ['dealerId'] });
                    if (dealerUser?.dealerId) {
                        const dealerProfile = await Dealer.findByPk(dealerUser.dealerId, { attributes: ['asmId'] });
                        if (dealerProfile?.asmId) participantIds.add(dealerProfile.asmId);
                    }
                }
                if (district.zmId) participantIds.add(district.zmId);
                if (district.region?.rbmId) participantIds.add(district.region.rbmId);
                if (district.zone?.zbhId) participantIds.add(district.zone.zbhId);
            }

            // 2. National roles
            const nationalRoles = [
                ROLES.DD_LEAD, 
                ROLES.DD_HEAD,
                ROLES.NBH, 
                ROLES.CCO,
                ROLES.CEO,
                ROLES.DD_ADMIN, 
                ROLES.LEGAL_ADMIN,
                ROLES.SUPER_ADMIN
            ];
            
            const nationalUsers = await User.findAll({
                where: {
                    roleCode: { [Op.in]: nationalRoles },
                    status: 'active'
                },
                attributes: ['id']
            });

            nationalUsers.forEach((u: any) => participantIds.add(u.id));

            // 3. Add all unique participants
            let addedCount = 0;
            for (const userId of participantIds) {
                const pType = userId === relocation.dealerId ? 'owner' : 'contributor';
                await this.addParticipant(relocation.id, REQUEST_TYPES.RELOCATION, userId, pType);
                addedCount++;
            }

            console.log(`[ParticipantService] Added ${addedCount} participants to relocation ${requestId}`);
        } catch (error) {
            console.error('Error assigning relocation participants:', error);
        }
    }

    /**
     * Assign participants for F&F Settlement (Sub-application)
     */
    /**
     * Assign participants for F&F Settlement (Sub-application) - Strictly limited to 8 National Roles
     */
    static async assignFnFParticipants(fnfId: string) {
        try {
            if (!await isAutoAssignmentEnabled('FNF')) {
                console.log(`[ParticipantService] Auto-assignment disabled for FNF. Skipping for ${fnfId}`);
                return;
            }
            const fnf = await db.FnF.findByPk(fnfId);
            if (!fnf) return;

            const participantIds = new Set<string>();

            // 1. National roles ONLY (Requested by user)
            const nationalRoles = [
                ROLES.DD_LEAD, 
                ROLES.DD_HEAD,
                ROLES.NBH, 
                ROLES.CCO,
                ROLES.CEO,
                ROLES.FINANCE, 
                ROLES.LEGAL_ADMIN,
                ROLES.SUPER_ADMIN
            ];
            
            const nationalUsers = await User.findAll({
                where: {
                    roleCode: { [Op.in]: nationalRoles },
                    status: 'active'
                },
                attributes: ['id']
            });

            nationalUsers.forEach((u: any) => participantIds.add(u.id));

            // 2. Add all unique participants as contributors
            let addedCount = 0;
            for (const userId of participantIds) {
                await this.addParticipant(fnf.id, REQUEST_TYPES.FNF, userId, 'contributor');
                addedCount++;
            }

            console.log(`[ParticipantService] Added ${addedCount} participants to F&F settlement ${fnfId}`);
        } catch (error) {
            console.error('Error assigning F&F participants:', error);
        }
    }

}
