using CMSMicroservice.Domain.Enums;
using Microsoft.EntityFrameworkCore;

namespace CMSMicroservice.Application.NetworkMembershipCQ.Queries.GetNetworkStatistics;

public class GetNetworkStatisticsQueryHandler : IRequestHandler<GetNetworkStatisticsQuery, GetNetworkStatisticsResponseDto>
{
    private readonly IApplicationDbContext _context;

    public GetNetworkStatisticsQueryHandler(
        IApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<GetNetworkStatisticsResponseDto> 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<long, int>();
        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<LevelDistributionModel>();
        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<long, int>();
        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();

        var (goldLeft, goldRight, silverLeft, silverRight) =
            await LoadPackageHolderCountsByLegAsync(leftChild, rightChild, allUsers, cancellationToken);

        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,
            GoldLeftLegTotal = goldLeft,
            GoldRightLegTotal = goldRight,
            SilverLeftLegTotal = silverLeft,
            SilverRightLegTotal = silverRight
        };
    }

    /// <summary>
    /// تعداد اعضای فعال باشگاه با پکیج جاری (IsCurrentCycle) در پای چپ/راست.
    /// </summary>
    private async Task<(int GoldLeft, int GoldRight, int SilverLeft, int SilverRight)> LoadPackageHolderCountsByLegAsync(
        User? leftChild,
        User? rightChild,
        List<User> allUsers,
        CancellationToken cancellationToken)
    {
        var leftLegUserIds = leftChild != null ? GetSubtreeUserIds(leftChild.Id, allUsers) : new HashSet<long>();
        var rightLegUserIds = rightChild != null ? GetSubtreeUserIds(rightChild.Id, allUsers) : new HashSet<long>();

        if (leftLegUserIds.Count == 0 && rightLegUserIds.Count == 0)
            return (0, 0, 0, 0);

        var (goldPackageId, silverPackageId) = await ResolveGoldSilverPackageIdsAsync(cancellationToken);
        if (!goldPackageId.HasValue && !silverPackageId.HasValue)
            return (0, 0, 0, 0);

        var allLegUserIds = leftLegUserIds.Union(rightLegUserIds).ToList();

        var userPackages = await (
            from cm in _context.ClubMemberships.AsNoTracking()
            join cc in _context.ClubMembershipCycles.AsNoTracking() on cm.UserId equals cc.UserId
            where cm.IsActive
                  && !cm.IsDeleted
                  && cc.IsCurrentCycle
                  && allLegUserIds.Contains(cm.UserId)
            select new { cm.UserId, cc.PackageId }
        ).ToListAsync(cancellationToken);

        var packageByUser = userPackages
            .GroupBy(x => x.UserId)
            .ToDictionary(g => g.Key, g => g.First().PackageId);

        int goldLeft = 0, goldRight = 0, silverLeft = 0, silverRight = 0;

        if (goldPackageId.HasValue)
        {
            goldLeft = leftLegUserIds.Count(id =>
                packageByUser.TryGetValue(id, out var packageId) && packageId == goldPackageId.Value);
            goldRight = rightLegUserIds.Count(id =>
                packageByUser.TryGetValue(id, out var packageId) && packageId == goldPackageId.Value);
        }

        if (silverPackageId.HasValue)
        {
            silverLeft = leftLegUserIds.Count(id =>
                packageByUser.TryGetValue(id, out var packageId) && packageId == silverPackageId.Value);
            silverRight = rightLegUserIds.Count(id =>
                packageByUser.TryGetValue(id, out var packageId) && packageId == silverPackageId.Value);
        }

        return (goldLeft, goldRight, silverLeft, silverRight);
    }

    private async Task<(long? GoldPackageId, long? SilverPackageId)> ResolveGoldSilverPackageIdsAsync(
        CancellationToken cancellationToken)
    {
        var packages = await _context.Packages
            .AsNoTracking()
            .Where(p => !p.IsDeleted && p.IsActive)
            .Select(p => new { p.Id, p.Title, p.IsBasePackage, p.MaxBalancesPerLeg })
            .ToListAsync(cancellationToken);

        var goldPackageId = packages.FirstOrDefault(p => p.IsBasePackage)?.Id
            ?? packages.FirstOrDefault(p => p.Title.Contains("طلایی") || p.Title.Contains("طلايی"))?.Id;

        var silverPackageId = packages.FirstOrDefault(p =>
                !p.IsBasePackage && (p.Title.Contains("نقره") || p.MaxBalancesPerLeg <= 30))?.Id
            ?? packages.FirstOrDefault(p => !p.IsBasePackage)?.Id;

        return (goldPackageId, silverPackageId);
    }

    private HashSet<long> GetSubtreeUserIds(long rootUserId, List<User> allUsers)
    {
        var ids = new HashSet<long> { rootUserId };
        foreach (var descendant in GetAllDescendants(rootUserId, allUsers))
            ids.Add(descendant.Id);
        return ids;
    }

    /// <summary>
    /// Recursively get all descendants of a user
    /// </summary>
    private List<User> GetAllDescendants(long userId, List<User> allUsers)
    {
        var descendants = new List<User>();
        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;
    }

    /// <summary>
    /// Calculate depth for all descendants recursively
    /// </summary>
    private void CalculateDepths(long userId, List<User> allUsers, int currentDepth, Dictionary<long, int> 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);
        }
    }
}
