namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyBalances;

public class CalculateWeeklyBalancesCommandHandler : IRequestHandler<CalculateWeeklyBalancesCommand, int>
{
    private readonly IApplicationDbContext _context;

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

    public async Task<int> Handle(CalculateWeeklyBalancesCommand request, CancellationToken cancellationToken)
    {
        // بررسی وجود محاسبه قبلی
        var existingBalances = await _context.NetworkWeeklyBalances
            .Where(x => x.WeekNumber == request.WeekNumber)
            .ToListAsync(cancellationToken);

        if (existingBalances.Any() && !request.ForceRecalculate)
        {
            throw new InvalidOperationException($"تعادل‌های هفته {request.WeekNumber} قبلاً محاسبه شده است. برای محاسبه مجدد از ForceRecalculate استفاده کنید");
        }

        // حذف محاسبات قبلی در صورت ForceRecalculate
        if (existingBalances.Any())
        {
            _context.NetworkWeeklyBalances.RemoveRange(existingBalances);
            await _context.SaveChangesAsync(cancellationToken);
        }

        // دریافت کاربران فعال در شبکه
        var usersInNetwork = await _context.Users
            .Where(x => x.NetworkParentId.HasValue)
            .Select(x => new { x.Id })
            .ToListAsync(cancellationToken);

        // دریافت باقیمانده‌های هفته قبل
        var previousWeekNumber = GetPreviousWeekNumber(request.WeekNumber);
        var previousWeekCarryovers = await _context.NetworkWeeklyBalances
            .Where(x => x.WeekNumber == previousWeekNumber)
            .Select(x => new
            {
                x.UserId,
                x.LeftLegRemainder,
                x.RightLegRemainder
            })
            .ToDictionaryAsync(x => x.UserId, cancellationToken);

        var balancesList = new List<NetworkWeeklyBalance>();
        var calculatedAt = DateTime.UtcNow;

        // خواندن یکباره Configuration ها (بهینه‌سازی - به جای N query)
        var configs = await _context.SystemConfigurations
            .Where(x => x.IsActive && (
                x.Key == "Club.ActivationFee" ||
                x.Key == "Commission.WeeklyPoolContributionPercent" ||
                x.Key == "Commission.MaxWeeklyBalancesPerLeg" ||
                x.Key == "Commission.MaxNetworkLevel"))
            .ToDictionaryAsync(x => x.Key, x => x.Value, cancellationToken);

        var activationFee = long.Parse(configs.GetValueOrDefault("Club.ActivationFee", "25000000"));
        var poolPercent = decimal.Parse(configs.GetValueOrDefault("Commission.WeeklyPoolContributionPercent", "20")) / 100m;
        // سقف تعادل هفتگی برای هر دست (نه کل) - 300 برای چپ + 300 برای راست = حداکثر 600 تعادل
        var maxBalancesPerLeg = int.Parse(configs.GetValueOrDefault("Commission.MaxWeeklyBalancesPerLeg", "300"));
        // حداکثر عمق شبکه برای شمارش اعضا (15 لول)
        var maxNetworkLevel = int.Parse(configs.GetValueOrDefault("Commission.MaxNetworkLevel", "15"));

        foreach (var user in usersInNetwork)
        {
            // دریافت باقیمانده هفته قبل
            var leftCarryover = 0;
            var rightCarryover = 0;
            if (previousWeekCarryovers.ContainsKey(user.Id))
            {
                leftCarryover = previousWeekCarryovers[user.Id].LeftLegRemainder;
                rightCarryover = previousWeekCarryovers[user.Id].RightLegRemainder;
            }

            // محاسبه تعداد اعضای جدید در این هفته (تا maxNetworkLevel لول پایین‌تر)
            var leftNewMembers = await CountNewMembersInLeg(user.Id, NetworkLeg.Left, request.WeekNumber, maxNetworkLevel, cancellationToken);
            var rightNewMembers = await CountNewMembersInLeg(user.Id, NetworkLeg.Right, request.WeekNumber, maxNetworkLevel, cancellationToken);

            // محاسبه مجموع هر پا (جدید + باقیمانده)
            var leftTotal = leftNewMembers + leftCarryover;
            var rightTotal = rightNewMembers + rightCarryover;

            // ✅ اصلاح شده: اعمال سقف روی هر دست جداگانه (نه روی کل)
            // سقف 300 برای دست چپ + 300 برای دست راست = حداکثر 600 تعادل در هفته
            var cappedLeftTotal = Math.Min(leftTotal, maxBalancesPerLeg);
            var cappedRightTotal = Math.Min(rightTotal, maxBalancesPerLeg);

            // محاسبه تعادل (کمترین مقدار بعد از اعمال سقف)
            var totalBalances = Math.Min(cappedLeftTotal, cappedRightTotal);

            // محاسبه باقیمانده برای هفته بعد
            // باقیمانده = مقداری که از سقف هر دست رد شده
            // مثال: چپ=350، راست=450، سقف=300
            // cappedLeft = MIN(350, 300) = 300
            // cappedRight = MIN(450, 300) = 300
            // totalBalances = MIN(300, 300) = 300
            // leftRemainder = 350 - 300 = 50 (مازاد سقف)
            // rightRemainder = 450 - 300 = 150 (مازاد سقف)
            var leftRemainder = leftTotal - cappedLeftTotal;
            var rightRemainder = rightTotal - cappedRightTotal;

            // محاسبه سهم استخر (20% از مجموع فعال‌سازی‌های جدید کل شبکه)
            // طبق گفته دکتر: کل افراد جدید در شبکه × هزینه فعال‌سازی × 20%
            var totalNewMembers = leftNewMembers + rightNewMembers;
            var weeklyPoolContribution = (long)(totalNewMembers * activationFee * poolPercent);

            var balance = new NetworkWeeklyBalance
            {
                UserId = user.Id,
                WeekNumber = request.WeekNumber,
                
                // اطلاعات جدید
                LeftLegNewMembers = leftNewMembers,
                RightLegNewMembers = rightNewMembers,
                LeftLegCarryover = leftCarryover,
                RightLegCarryover = rightCarryover,
                
                // مجموع
                LeftLegTotal = leftTotal,
                RightLegTotal = rightTotal,
                TotalBalances = totalBalances, // تعادل واقعی بعد از اعمال سقف روی هر دست
                
                // باقیمانده برای هفته بعد (مازاد سقف هر دست)
                LeftLegRemainder = leftRemainder,
                RightLegRemainder = rightRemainder,
                
                // فیلدهای قدیمی (deprecated) - برای سازگاری با کدهای قبلی
#pragma warning disable CS0618
                LeftLegBalances = leftTotal,
                RightLegBalances = rightTotal,
#pragma warning restore CS0618
                
                WeeklyPoolContribution = weeklyPoolContribution,
                CalculatedAt = calculatedAt,
                IsExpired = false
            };

            balancesList.Add(balance);
        }

        await _context.NetworkWeeklyBalances.AddRangeAsync(balancesList, cancellationToken);
        await _context.SaveChangesAsync(cancellationToken);

        return balancesList.Count;
    }

    /// <summary>
    /// شماره هفته قبل را محاسبه می‌کند
    /// </summary>
    private string GetPreviousWeekNumber(string currentWeekNumber)
    {
        // مثال: "2025-W48" -> "2025-W47"
        var parts = currentWeekNumber.Split('-');
        var year = int.Parse(parts[0]);
        var week = int.Parse(parts[1].Replace("W", ""));
        
        week--;
        if (week < 1)
        {
            year--;
            week = 52; // یا 53 بسته به سال
        }
        
        return $"{year}-W{week:D2}";
    }

    /// <summary>
    /// شمارش اعضای جدیدی که در این هفته به یک پا اضافه شدند
    /// تا maxLevel لول پایین‌تر شمارش می‌شود
    /// </summary>
    private async Task<int> CountNewMembersInLeg(long userId, NetworkLeg leg, string weekNumber, int maxLevel, CancellationToken cancellationToken)
    {
        // تبدیل WeekNumber به بازه تاریخی
        var (startDate, endDate) = GetWeekDateRange(weekNumber);

        // شمارش تمام اعضای زیرمجموعه که در این هفته فعال شدند (تا maxLevel لول)
        var count = await CountNewMembersRecursive(userId, leg, startDate, endDate, 0, maxLevel, cancellationToken);
        
        return count;
    }

    /// <summary>
    /// شمارش بازگشتی اعضای جدید در یک پا
    /// محدودیت عمق: تا maxLevel لول پایین‌تر شمارش می‌شود
    /// </summary>
    private async Task<int> CountNewMembersRecursive(
        long userId, 
        NetworkLeg leg, 
        DateTime startDate, 
        DateTime endDate, 
        int currentLevel,
        int maxLevel,
        CancellationToken cancellationToken)
    {
        // ⭐ محدودیت عمق: اگر به حداکثر لول رسیدیم، توقف
        if (currentLevel >= maxLevel)
        {
            return 0;
        }

        // پیدا کردن فرزند مستقیم در پای مورد نظر
        var child = await _context.Users
            .FirstOrDefaultAsync(x => x.NetworkParentId == userId && x.LegPosition == leg, cancellationToken);

        if (child == null)
        {
            return 0;
        }

        var count = 0;

        // اگر فرزند در این هفته فعال شده، 1 امتیاز
        var membership = await _context.ClubMemberships
            .FirstOrDefaultAsync(x => x.UserId == child.Id && x.IsActive, cancellationToken);

        if (membership?.ActivatedAt >= startDate && membership?.ActivatedAt <= endDate)
        {
            count = 1;
        }

        // جمع کردن اعضای جدید از پای چپ و راست فرزند (با افزایش لول)
        var childLeft = await CountNewMembersRecursive(child.Id, NetworkLeg.Left, startDate, endDate, currentLevel + 1, maxLevel, cancellationToken);
        var childRight = await CountNewMembersRecursive(child.Id, NetworkLeg.Right, startDate, endDate, currentLevel + 1, maxLevel, cancellationToken);

        return count + childLeft + childRight;
    }

    /// <summary>
    /// تبدیل شماره هفته به بازه تاریخی
    /// </summary>
    private (DateTime startDate, DateTime endDate) GetWeekDateRange(string weekNumber)
    {
        // مثال: "2025-W48"
        var parts = weekNumber.Split('-');
        var year = int.Parse(parts[0]);
        var week = int.Parse(parts[1].Replace("W", ""));

        // محاسبه اولین روز هفته (شنبه)
        var jan1 = new DateTime(year, 1, 1);
        var daysOffset = DayOfWeek.Saturday - jan1.DayOfWeek;
        var firstSaturday = jan1.AddDays(daysOffset);
        var weekStart = firstSaturday.AddDays((week - 1) * 7);
        var weekEnd = weekStart.AddDays(6).AddHours(23).AddMinutes(59).AddSeconds(59);

        return (weekStart, weekEnd);
    }
}
