using CMSMicroservice.Domain.Enums; namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStatistics; public class GetNetworkStatisticsQueryHandler : IRequestHandler { private readonly IApplicationDbContext _context; public GetNetworkStatisticsQueryHandler( IApplicationDbContext context) { _context = context; } public async Task Handle(GetNetworkStatisticsQuery request, CancellationToken cancellationToken) { // UserId > 0 → stats for that user's network // UserId == 0 → global stats (find root user of the network) var userId = request.UserId; // Get all users (needed for descendant calculation anyway) var allUsers = await _context.Users.ToListAsync(cancellationToken); if (userId == 0) { // Find root user (user with no NetworkParentId) var rootUser = allUsers.FirstOrDefault(x => x.NetworkParentId == null || x.NetworkParentId == 0); if (rootUser != null) userId = rootUser.Id; else if (allUsers.Count > 0) userId = allUsers.First().Id; else throw new InvalidOperationException("No users found in the system"); } var allDescendants = GetAllDescendants(userId, allUsers); // Statistics for the user's network (all descendants) var totalMembers = allDescendants.Count; var activeMembers = allDescendants.Count(x => !x.IsDeleted); // Get direct left and right children var leftChild = allUsers.FirstOrDefault(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Left); var rightChild = allUsers.FirstOrDefault(x => x.NetworkParentId == userId && x.LegPosition == NetworkLeg.Right); // Count all descendants in left and right subtrees var leftLegCount = leftChild != null ? GetAllDescendants(leftChild.Id, allUsers).Count + 1 : 0; // +1 for leftChild itself var rightLegCount = rightChild != null ? GetAllDescendants(rightChild.Id, allUsers).Count + 1 : 0; // +1 for rightChild itself double leftPercentage = totalMembers > 0 ? (leftLegCount / (double)totalMembers) * 100 : 0; double rightPercentage = totalMembers > 0 ? (rightLegCount / (double)totalMembers) * 100 : 0; // Calculate actual depth int maxDepth = 0; double totalDepthSum = 0; var userDepths = new Dictionary(); CalculateDepths(userId, allUsers, 0, userDepths, ref maxDepth); if (allDescendants.Count > 0) { totalDepthSum = allDescendants.Sum(d => userDepths.ContainsKey(d.Id) ? userDepths[d.Id] : 0); } double averageDepth = allDescendants.Count > 0 ? totalDepthSum / allDescendants.Count : 0; // Level distribution - calculate from depths var levelDistribution = new List(); if (allDescendants.Count > 0) { var levelCounts = allDescendants .Where(d => userDepths.ContainsKey(d.Id)) .GroupBy(d => userDepths[d.Id]) .OrderBy(g => g.Key) .Select(g => new LevelDistributionModel { Level = g.Key, Count = g.Count() }) .ToList(); levelDistribution = levelCounts; } // Monthly growth (last 6 months) - using descendants Created date var sixMonthsAgo = DateTime.Now.AddMonths(-6); var monthlyGrowthRaw = allDescendants .Where(x => x.Created >= sixMonthsAgo) .Select(x => new { x.Created.Year, x.Created.Month }) .ToList(); var monthlyGrowth = monthlyGrowthRaw .GroupBy(x => new { x.Year, x.Month }) .Select(g => new MonthlyGrowthModel { Month = $"{g.Key.Year}-{g.Key.Month:D2}", NewMembers = g.Count() }) .OrderBy(x => x.Month) .ToList(); // Top users by total descendants count var userDescendantCounts = new Dictionary(); foreach (var user in allDescendants) { var descendants = GetAllDescendants(user.Id, allUsers); userDescendantCounts[user.Id] = descendants.Count; } var topUserData = allDescendants .Where(x => x.Id != userId && userDescendantCounts[x.Id] > 0) .Select(x => new { x.Id, UserName = (x.FirstName + " " + x.LastName).Trim(), DescendantCount = userDescendantCounts[x.Id], LeftCount = allUsers.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Left), RightCount = allUsers.Count(c => c.NetworkParentId == x.Id && c.LegPosition == NetworkLeg.Right) }) .OrderByDescending(x => x.DescendantCount) .Take(10) .ToList(); var topUserModels = topUserData.Select((x, index) => new TopNetworkUserModel { Rank = index + 1, UserId = x.Id, UserName = x.UserName, TotalChildren = x.DescendantCount, LeftCount = x.LeftCount, RightCount = x.RightCount }).ToList(); return new GetNetworkStatisticsResponseDto { TotalMembers = totalMembers, ActiveMembers = activeMembers, LeftLegCount = leftLegCount, RightLegCount = rightLegCount, LeftPercentage = leftPercentage, RightPercentage = rightPercentage, AverageDepth = averageDepth, MaxDepth = maxDepth, LevelDistribution = levelDistribution, MonthlyGrowth = monthlyGrowth, TopUsers = topUserModels }; } /// /// Recursively get all descendants of a user /// private List GetAllDescendants(long userId, List allUsers) { var descendants = new List(); var directChildren = allUsers.Where(x => x.NetworkParentId == userId).ToList(); foreach (var child in directChildren) { descendants.Add(child); descendants.AddRange(GetAllDescendants(child.Id, allUsers)); } return descendants; } /// /// Calculate depth for all descendants recursively /// private void CalculateDepths(long userId, List allUsers, int currentDepth, Dictionary depths, ref int maxDepth) { var children = allUsers.Where(x => x.NetworkParentId == userId).ToList(); foreach (var child in children) { var childDepth = currentDepth + 1; depths[child.Id] = childDepth; if (childDepth > maxDepth) maxDepth = childDepth; CalculateDepths(child.Id, allUsers, childDepth, depths, ref maxDepth); } } }