import { Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import { Op } from 'sequelize';
import db from '../../database/models/index.js';
const { Role, Permission, RolePermission, User, DealerCode, AuditLog } = db;
import { AUDIT_ACTIONS, ROLES } from '../../common/config/constants.js';
import { AuthRequest } from '../../types/express.types.js';
import { syncLocationManagers, syncRegionManager, syncZoneManager } from '../master/syncHierarchy.service.js';
import { resolveManagerCode } from '../../services/userRoleCode.service.js';

const upsertUserAssignments = async (
    userId: string,
    assignments: any[],
    actorUserId?: string
) => {
    if (!Array.isArray(assignments)) return;

    await db.UserRole.destroy({ where: { userId } });

    for (let i = 0; i < assignments.length; i++) {
        const assignment = assignments[i] || {};
        const roleCode = assignment.roleCode || assignment.role;
        if (!roleCode) continue;

        const role = await Role.findOne({ where: { roleCode } });
        if (!role) continue;
        const managerCode = await resolveManagerCode(role.id, roleCode, null);

        const createdRole = await db.UserRole.create({
            userId,
            roleId: role.id,
            districtId: assignment.locationId || assignment.districtId || null,
            zoneId: assignment.zoneId || null,
            regionId: assignment.regionId || null,
            managerCode,
            isPrimary: assignment.isPrimary !== undefined ? Boolean(assignment.isPrimary) : i === 0,
            isActive: assignment.isActive !== undefined ? Boolean(assignment.isActive) : true,
            effectiveFrom: assignment.effectiveFrom || null,
            effectiveTo: assignment.effectiveTo || null,
            assignedBy: actorUserId || null
        });

        // Trigger Sync
        const targetId = assignment.locationId || assignment.districtId;
        if (targetId) await syncLocationManagers(targetId);
        if (assignment.regionId) await syncRegionManager(assignment.regionId);
        if (assignment.zoneId) await syncZoneManager(assignment.zoneId);
    }
};

// --- Roles Management ---

export const getRoles = async (req: Request, res: Response) => {
    try {
        const roles = await Role.findAll({
            include: [
                {
                    model: Permission,
                    as: 'permissions',
                    through: { attributes: [] }
                },
                {
                    model: User,
                    as: 'users',
                    attributes: ['id']
                }
            ],
            order: [['roleName', 'ASC']]
        });

        // Map to include userCount
        const result = roles.map((r: any) => ({
            ...r.toJSON(),
            userCount: r.users?.length || 0
        }));

        res.json({ success: true, data: result });
    } catch (error) {
        console.error('Get roles error:', error);
        res.status(500).json({ success: false, message: 'Error fetching roles' });
    }
};

export const createRole = async (req: AuthRequest, res: Response) => {
    try {
        const { roleCode, roleName, description, permissionIds } = req.body; // permissionIds: string[]

        const role = await Role.create({ roleCode, roleName, description });

        if (permissionIds && permissionIds.length > 0) {
            for (const pid of permissionIds) {
                await RolePermission.create({
                    roleId: role.id,
                    permissionId: pid
                });
            }
        }

        await AuditLog.create({
            userId: req.user?.id,
            action: AUDIT_ACTIONS.CREATED,
            entityType: 'role',
            entityId: role.id,
            newData: req.body
        });

        res.status(201).json({ success: true, data: role, message: 'Role created successfully' });
    } catch (error) {
        console.error('Create role error:', error);
        res.status(500).json({ success: false, message: 'Error creating role' });
    }
};

export const updateRole = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params;
        const { roleName, description, permissionIds, permissions, isActive } = req.body;
        const permsToUpdate = permissionIds || permissions;

        const role = await Role.findByPk(id);
        if (!role) return res.status(404).json({ success: false, message: 'Role not found' });

        await role.update({ roleName, description, isActive });

        if (permsToUpdate && Array.isArray(permsToUpdate)) {
            // Resolve IDs if they are passed as codes
            const resolvedIds: string[] = [];
            for (const pid of permsToUpdate) {
                if (pid.includes(':') || /^[A-Z_]+$/.test(pid)) {
                    const perm = await Permission.findOne({ where: { permissionCode: pid } });
                    if (perm) resolvedIds.push(perm.id);
                } else {
                    resolvedIds.push(pid);
                }
            }

            // Remove existing permissions and re-add new ones
            await RolePermission.destroy({ where: { roleId: id } });
            for (const resolvedId of resolvedIds) {
                await RolePermission.create({
                    roleId: id,
                    permissionId: resolvedId
                });
            }
        }

        await AuditLog.create({
            userId: req.user?.id,
            action: AUDIT_ACTIONS.UPDATED,
            entityType: 'role',
            entityId: id,
            newData: req.body
        });

        res.json({ success: true, message: 'Role updated successfully' });
    } catch (error) {
        console.error('Update role error:', error);
        res.status(500).json({ success: false, message: 'Error updating role' });
    }
};

// --- Permissions Management ---

export const getPermissions = async (req: Request, res: Response) => {
    try {
        const permissions = await Permission.findAll({ order: [['module', 'ASC']] });
        res.json({ success: true, data: permissions });
    } catch (error) {
        console.error('Get permissions error:', error);
        res.status(500).json({ success: false, message: 'Error fetching permissions' });
    }
};

// --- User Management (Admin) ---

export const getAllUsers = async (req: Request, res: Response) => {
    try {
        const { roleCode, locationId, search, page = 1, limit = 100, isExternal } = req.query as any;
        const whereClause: any = {};
        const andConditions: any[] = [];

        // 0. External filter
        if (isExternal !== undefined) {
            whereClause.isExternal = isExternal === 'true';
        }

        // 1. Search filter
        if (search) {
            andConditions.push({
                [Op.or]: [
                { fullName: { [Op.iLike]: `%${search}%` } },
                { email: { [Op.iLike]: `%${search}%` } },
                { employeeId: { [Op.iLike]: `%${search}%` } }
                ]
            });
        }

        // 2. Role filter
        let rawRoleCode: any = roleCode || req.query['roleCode[]'];
        let finalRoleCodes: string[] = [];

        if (rawRoleCode) {
            if (Array.isArray(rawRoleCode)) {
                finalRoleCodes = rawRoleCode;
            } else if (typeof rawRoleCode === 'string') {
                finalRoleCodes = rawRoleCode.split(',').map(r => r.trim());
            }
        }

        if (finalRoleCodes.length > 0) {
            whereClause.roleCode = { [Op.in]: finalRoleCodes };
        }

        const nationalRoles = [
            ROLES.NBH, 
            ROLES.DD_HEAD, 
            ROLES.DD_LEAD, 
            ROLES.DD_ADMIN, 
            ROLES.LEGAL_ADMIN, 
            ROLES.FINANCE, 
            ROLES.SUPER_ADMIN,
            ROLES.CCO,
            ROLES.CEO
        ];
        const isNationalRole = finalRoleCodes.some(r => (nationalRoles as readonly string[]).includes(r));

        if (!isNationalRole && locationId) {
            const district: any = await db.District.findByPk(locationId as string, {
                attributes: ['id', 'zoneId', 'regionId', 'stateId']
            });
            
            if (district) {
                const relevantIds = [district.id, district.zoneId, district.regionId, district.stateId].filter(Boolean);
                andConditions.push({
                    [Op.or]: [
                    { districtId: { [Op.in]: relevantIds } },
                    { zoneId: { [Op.in]: relevantIds } },
                    { regionId: { [Op.in]: relevantIds } },
                    { stateId: { [Op.in]: relevantIds } },
                    { '$userRoles.districtId$': { [Op.in]: relevantIds } },
                    { '$userRoles.zoneId$': { [Op.in]: relevantIds } },
                    { '$userRoles.regionId$': { [Op.in]: relevantIds } }
                    ]
                });
            }
        }

        if (andConditions.length > 0) {
            whereClause[Op.and] = andConditions;
        }

        const { count, rows: users } = await User.findAndCountAll({
            where: whereClause,
            attributes: { exclude: ['password'] },
            limit: Number(limit),
            offset: (Number(page) - 1) * Number(limit),
            subQuery: false,
            include: [
                {
                    model: Role,
                    as: 'role',
                    include: [
                        {
                            model: Permission,
                            as: 'permissions',
                            through: { attributes: [] }
                        }
                    ]
                },
                { model: db.District, as: 'district' },
                {
                    model: db.UserRole,
                    as: 'userRoles',
                    include: [
                        { 
                            model: db.District, 
                            as: 'district', 
                            attributes: ['id', 'name', 'stateId', 'regionId', 'zoneId'],
                            include: [
                                { model: db.State, as: 'state', attributes: ['id', 'name'] },
                                { model: db.Region, as: 'region', attributes: ['id', 'name'] },
                                { model: db.Zone, as: 'zone', attributes: ['id', 'name'] }
                            ]
                        },
                        { model: db.Zone, as: 'zone', attributes: ['id', 'name'] },
                        { model: db.Region, as: 'region', attributes: ['id', 'name'] }
                    ]
                }
            ],
            order: [['createdAt', 'DESC']],
            distinct: true
        });

        const result = users.map((u: any) => {
            const userJson = u.toJSON();
            const assignments = userJson.userRoles || [];

            // territories mapping — provide fallbacks to the nested location fields
            const territories = assignments.map((a: any) => ({
                role: a.role?.roleName,
                roleCode: a.role?.roleCode,
                districtId: a.districtId,
                districtName: a.district?.name,
                locationType: 'district',
                managerCode: a.managerCode,
                zoneId: a.zoneId || a.district?.zoneId,
                zone: a.zone?.name || a.district?.zone?.name,
                regionId: a.regionId || a.district?.regionId,
                region: a.region?.name || a.district?.region?.name,
                stateId: a.district?.state?.id || a.district?.stateId,
                state: a.district?.state?.name,
                isActive: a.isActive
            }));

            userJson.territoryProfile = territories;
            userJson.allRoles = Array.from(new Set([
                u.role?.roleCode,
                u.role?.roleName,
                ...assignments.flatMap((a: any) => [a.role?.roleCode, a.role?.roleName])
            ].filter(Boolean)));
            userJson.allZones = Array.from(new Set(
                territories.map((t: any) => t.zone).filter(Boolean).map((z: string) => z.toUpperCase())
            ));
            userJson.allRegions = Array.from(new Set(
                territories.map((t: any) => t.region).filter(Boolean).map((r: string) => r.toUpperCase())
            ));

            return userJson;
        });

        res.json({ 
            success: true, 
            data: result, 
            meta: {
                total: count,
                totalPages: Math.ceil(count / Number(limit)),
                currentPage: Number(page),
                limit: Number(limit)
            }
        });
    } catch (error) {
        console.error('Get users error:', error);
        res.status(500).json({ success: false, message: 'Error fetching users' });
    }
}

export const createUser = async (req: AuthRequest, res: Response) => {
    try {
        const {
            fullName, email, roleCode,
            employeeId, mobileNumber, department, designation,
            locationId,
            assignments,
            districts, // New: ASM managed areas
            regionIds, // New: ZM managed regions
            asmCode,   // New: ASM code
            zmCode     // New: ZM code
        } = req.body;


        // Validate required fields
        if (!fullName || !email || !roleCode) {
            return res.status(400).json({
                success: false,
                message: 'Full Name, Email, and Role are required'
            });
        }

        // Check if user already exists (Email)
        const existingEmail = await User.findOne({ where: { email } });
        if (existingEmail) {
            return res.status(400).json({
                success: false,
                message: 'User with this email already exists'
            });
        }

        // Check if user already exists (Employee ID)
        if (employeeId) {
            const existingEmpId = await User.findOne({ where: { employeeId } });
            if (existingEmpId) {
                return res.status(400).json({
                    success: false,
                    message: `User with Employee ID ${employeeId} already exists`
                });
            }
        }

        // Hash default password
        const hashedPassword = await bcrypt.hash('Admin@123', 10);

        // Location is optional. Only persist to user.districtId if the value is a valid district UUID.
        let safeDistrictId: string | null = null;
        if (locationId) {
            const districtExists = await db.District.findByPk(locationId);
            safeDistrictId = districtExists ? locationId : null;
        }

        // Create user
        const user = await User.create({
            fullName,
            email,
            password: hashedPassword,
            roleCode,
            status: 'active',
            isActive: true,
            employeeId,
            mobileNumber,
            department,
            designation,
            districtId: safeDistrictId
        });

        if (Array.isArray(assignments) && assignments.length > 0) {
            await upsertUserAssignments(user.id, assignments, req.user?.id);
        } else if (districts && Array.isArray(districts) && ['ASM', 'DD-AM'].includes(roleCode)) {
            const targetRole = await Role.findOne({ where: { roleCode } });
            if (targetRole) {
                for (const distId of districts) {
                    const sampleDistrict = await db.District.findByPk(distId);
                    const managerCode = await resolveManagerCode(targetRole.id, roleCode, null);
                    await db.UserRole.create({
                        userId: user.id,
                        roleId: targetRole.id,
                        districtId: distId,
                        zoneId: sampleDistrict?.zoneId || null,
                        regionId: sampleDistrict?.regionId || null,
                        managerCode,
                        isPrimary: false,
                        isActive: true,
                        assignedBy: req.user?.id || null
                    });
                    await syncLocationManagers(distId);
                }
            }
        } else if ((regionIds || districts) && (roleCode === 'ZM' || roleCode === 'DD-ZM')) {
            const targetRole = await Role.findOne({ where: { roleCode: roleCode } });
            if (targetRole) {
                // If districts are passed for ZM, we convert them to unique regionIds for consistency
                let finalRegionIds = regionIds || [];
                if (finalRegionIds.length === 0 && districts && districts.length > 0) {
                    const mappedDistricts = await db.District.findAll({ where: { id: { [Op.in]: districts } } });
                    finalRegionIds = Array.from(new Set(mappedDistricts.map((d: any) => d.regionId).filter(Boolean)));
                }

                for (const regId of finalRegionIds) {
                    const region = await db.Region.findByPk(regId);
                    const managerCode = await resolveManagerCode(targetRole.id, roleCode, null);
                    await db.UserRole.create({
                        userId: user.id,
                        roleId: targetRole.id,
                        regionId: regId,
                        zoneId: region?.zoneId || null,
                        managerCode,
                        isActive: true,
                        assignedBy: req.user?.id || null
                    });
                    
                    // Trigger sync for all districts in this region to update legacy zmId
                    const regionDistricts = await db.District.findAll({ where: { regionId: regId } });
                    for (const d of regionDistricts) await syncLocationManagers(d.id);
                }
            }
        } else if (roleCode) {
            const role = await Role.findOne({ where: { roleCode } });
            if (role) {
                const managerCode = await resolveManagerCode(role.id, roleCode, null);
                await db.UserRole.create({
                    userId: user.id,
                    roleId: role.id,
                    districtId: safeDistrictId,
                    managerCode,
                    isPrimary: true,
                    isActive: true,
                    assignedBy: req.user?.id || null
                });
            }
        }

        await AuditLog.create({
            userId: req.user?.id,
            action: AUDIT_ACTIONS.CREATED,
            entityType: 'user',
            entityId: user.id,
            newData: req.body
        });

        res.status(201).json({ success: true, message: 'User created successfully', data: user });
    } catch (error: any) {
        console.error('Create user error:', error);
        if (error.name === 'SequelizeUniqueConstraintError') {
            const field = error.errors[0]?.path || 'field';
            const value = error.errors[0]?.value || '';
            return res.status(400).json({ 
                success: false, 
                message: `${field} "${value}" already exists. Please use a unique value.` 
            });
        }
        res.status(500).json({ success: false, message: 'Error creating user' });
    }
};

export const updateUserStatus = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params;
        const { status, isActive } = req.body;

        const user = await User.findByPk(id);
        if (!user) return res.status(404).json({ success: false, message: 'User not found' });

        await user.update({ status, isActive });
        
        // If user is deactivated, clear their ASM assignments in District table
        if (isActive === false) {
            await db.District.update({ asmId: null }, { where: { asmId: id } });
        }

        await AuditLog.create({
            userId: req.user?.id,
            action: AUDIT_ACTIONS.UPDATED,
            entityType: 'user',
            entityId: id,
            newData: { status, isActive }
        });

        res.json({ success: true, message: 'User status updated' });
    } catch (error) {
        console.error('Update user status error:', error);
        res.status(500).json({ success: false, message: 'Error updating user status' });
    }
};

export const updateUser = async (req: AuthRequest, res: Response) => {
    try {
        const { id } = req.params;
        const {
            fullName, email, roleCode, status, isActive, employeeId,
            mobileNumber, department, designation,
            locationId,
            assignments,
            districts, // New: ASM managed areas
            regionIds, // New: ZM managed regions
            asmCode,   // New: ASM code
            zmCode,    // New: ZM code
            password // Optional password update
        } = req.body;

        const user = await User.findByPk(id);
        if (!user) return res.status(404).json({ success: false, message: 'User not found' });

        const oldData = user.toJSON();
        const updates: any = {
            fullName: fullName || user.fullName,
            email: email || user.email,
            roleCode: roleCode || user.roleCode,
            status: status || user.status,
            isActive: isActive !== undefined ? isActive : user.isActive,
            employeeId: employeeId || user.employeeId,
            mobileNumber: mobileNumber || user.mobileNumber,
            department: department || user.department,
            designation: designation || user.designation
        };

        // NEW: Validate locationId if provided (must exist in districts table, otherwise set to null on User record)
        if (locationId !== undefined) {
            if (locationId === '' || locationId === null) {
                updates.districtId = null;
            } else {
                const districtExists = await db.District.findByPk(locationId);
                updates.districtId = districtExists ? locationId : null;
            }
        }

        // If password is provided, hash it and update
        if (password && password.trim() !== '') {
            updates.password = await bcrypt.hash(password, 10);
        }

        await user.update(updates);

        if (Array.isArray(assignments)) {
            await upsertUserAssignments(id as string, assignments, req.user?.id);
        } else if (districts && Array.isArray(districts) && ['ASM', 'DD-AM'].includes(roleCode)) {
            const targetRole = await Role.findOne({ where: { roleCode } });
            if (!targetRole) throw new Error(`${roleCode} role not found`);

            await db.UserRole.destroy({ where: { userId: id, roleId: targetRole.id } });
            if (roleCode === 'ASM') {
                await db.District.update({ asmId: null, asmCode: null }, { where: { asmId: id } });
            } else {
                await db.District.update({ ddAmId: null, ddAmCode: null }, { where: { ddAmId: id } });
            }

            for (const distId of districts) {
                const sampleDistrict = await db.District.findByPk(distId);
                const managerCode = await resolveManagerCode(targetRole.id, roleCode, null);
                await db.UserRole.create({
                    userId: id,
                    roleId: targetRole.id,
                    districtId: distId,
                    zoneId: sampleDistrict?.zoneId || null,
                    regionId: sampleDistrict?.regionId || null,
                    managerCode,
                    isActive: true,
                    assignedBy: req.user?.id || null
                });
                await syncLocationManagers(distId);
            }
        } else if ((regionIds || districts) && (roleCode === 'ZM' || roleCode === 'DD-ZM')) {
            const targetRole = await Role.findOne({ where: { roleCode: roleCode } });
            if (!targetRole) throw new Error(`${roleCode} role not found`);

            await db.UserRole.destroy({ where: { userId: id, roleId: targetRole.id } });
            await db.District.update({ zmId: null, zmCode: null }, { where: { zmId: id } });

            let finalRegionIds = regionIds || [];
            if (finalRegionIds.length === 0 && districts && districts.length > 0) {
                const mappedDistricts = await db.District.findAll({ where: { id: { [Op.in]: districts } } });
                finalRegionIds = Array.from(new Set(mappedDistricts.map((d: any) => d.regionId).filter(Boolean)));
            }

            for (const regId of finalRegionIds) {
                const region = await db.Region.findByPk(regId);
                const managerCode = await resolveManagerCode(targetRole.id, roleCode, null);
                await db.UserRole.create({
                    userId: id,
                    roleId: targetRole.id,
                    regionId: regId,
                    zoneId: region?.zoneId || null,
                    managerCode,
                    isActive: true,
                    assignedBy: req.user?.id || null
                });
                
                const regionDistricts = await db.District.findAll({ where: { regionId: regId } });
                for (const d of regionDistricts) await syncLocationManagers(d.id);
            }
        }
 else if (roleCode !== undefined || locationId !== undefined) {
            const primaryRoleCode = roleCode || user.roleCode;
            if (primaryRoleCode) {
                const role = await Role.findOne({ where: { roleCode: primaryRoleCode } });
                if (role) {
                    await db.UserRole.destroy({ where: { userId: id, isPrimary: true } });
                    const created = await db.UserRole.create({
                        userId: id,
                        roleId: role.id,
                        districtId: updates.districtId ?? user.districtId ?? null,
                        isPrimary: true,
                        isActive: updates.isActive,
                        assignedBy: req.user?.id || null
                    });
                    // Sync primary location if exists
                    if (created.districtId) await syncLocationManagers(created.districtId);
                    if (created.regionId) await syncRegionManager(created.regionId);
                    if (created.zoneId) await syncZoneManager(created.zoneId);
                }
            }
        }

        await AuditLog.create({
            userId: req.user?.id,
            action: AUDIT_ACTIONS.UPDATED,
            entityType: 'user',
            entityId: id,
            oldData,
            newData: req.body
        });

        res.json({ success: true, message: 'User updated successfully', data: user });
    } catch (error: any) {
        console.error('Update user error:', error);
        if (error.name === 'SequelizeUniqueConstraintError') {
            const field = error.errors[0]?.path || 'field';
            const value = error.errors[0]?.value || '';
            return res.status(400).json({ 
                success: false, 
                message: `${field} "${value}" already exists. Please use a unique value.` 
            });
        }
        res.status(500).json({ success: false, message: 'Error updating user' });
    }
};

// --- Dealer Codes ---

export const generateDealerCode = async (req: AuthRequest, res: Response) => {
    try {
        const { locationId, channel } = req.body;
        // Logic to generate unique code based on format (e.g., RE-[Region]-[State]-[Seq])
        // This is a placeholder for the actual business logic

        const timestamp = Date.now().toString().slice(-6);
        const code = `DLR-${timestamp}`;

        const dealerCode = await DealerCode.create({
            code,
            isUsed: false,
            generatedBy: req.user?.id
        });

        res.status(201).json({ success: true, data: dealerCode });
    } catch (error) {
        console.error('Generate dealer code error:', error);
        res.status(500).json({ success: false, message: 'Error generating dealer code' });
    }
};
