using CMSMicroservice.Domain.Common;

namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyBalances;

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

    public CalculateWeeklyBalancesCommandHandler(
        IApplicationDbContext context,
        IWeekDefinitionRepository weekRepository)
    {
        _context = context;
        _weekRepository = weekRepository;
    }

    public async Task<int> Handle(CalculateWeeklyBalancesCommand request, CancellationToken cancellationToken)
    {
       
        var regorianWeekNumber = _weekRepository.GetGregorianWeekNumber(request.WeekDefinitionId);
        if (regorianWeekNumber == null)
        {
            throw new InvalidOperationException($"هفته {request.WeekDefinitionId} در سیستم تعریف نشده است");
        }
        
        // بررسی وجود محاسبه قبلی
        var existingBalances = await _context.NetworkWeeklyBalances
            .Where(x => x.WeekDefinitionId == request.WeekDefinitionId)
            .ToListAsync(cancellationToken);

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

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

        // ⭐ دریافت همه کاربرانی که عضو فعال باشگاه هستند
        // بدون محدودیت زمانی - همه اعضای فعال کلاب باید کمیسیون بگیرند
        // ⚠️ Magic Wallet: کاربرهایی که در حالت جادویی هستند از کمیسیون خارج میشن
        var magicModeUserIds = await _context.UserWallets
            .Where(w => w.WalletMode == WalletMode.Magic)
            .Select(w => w.UserId)
            .ToHashSetAsync(cancellationToken);

        var activeClubMemberUserIds = await _context.ClubMemberships
            .Where(c => c.IsActive && !magicModeUserIds.Contains(c.UserId))
            .Select(c => c.UserId)
            .ToHashSetAsync(cancellationToken);

        // دریافت کاربران فعال در شبکه که عضو باشگاه هستند
        // نکته: شرط NetworkParentId.HasValue نداریم چون ریشه شبکه (اولین نفر) هم باید حساب بشه
        var usersInNetwork = await _context.Users
            .Where(x => activeClubMemberUserIds.Contains(x.Id))
            .Select(x => new { x.Id })
            .ToListAsync(cancellationToken);

        // دریافت باقیمانده‌های هفته قبل
        var previousWeekDefinitionId = GetPreviousWeekDefinitionId(request.WeekDefinitionId);
        Dictionary<long, (int LeftLegRemainder, int RightLegRemainder)> previousWeekCarryovers;
        
        if (previousWeekDefinitionId.HasValue)
        {
            previousWeekCarryovers = await _context.NetworkWeeklyBalances
                .Where(x => x.WeekDefinitionId == previousWeekDefinitionId.Value)
                .ToDictionaryAsync(
                    x => x.UserId, 
                    x => (x.LeftLegRemainder, x.RightLegRemainder), 
                    cancellationToken);
        }
        else
        {
            previousWeekCarryovers = new Dictionary<long, (int, int)>();
        }

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

        // بارگذاری پکیج پایه برای خواندن سقف‌ها
        var package = await _context.Packages
            .FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken)
            ?? throw new InvalidOperationException("پکیج پایه یافت نشد");
        
        // خواندن سقف‌ها از پکیج (قبلاً از SystemConstants بود)
        var maxBalancesPerLeg = package.MaxBalancesPerLeg;
        var maxNetworkLevel = package.MaxNetworkLevel;

        foreach (var user in usersInNetwork.OrderBy(o=>o.Id))
        {
            // دریافت باقیمانده هفته قبل
            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.WeekDefinitionId, maxNetworkLevel, cancellationToken);
            var rightNewMembers = await CountNewMembersInLeg(user.Id, NetworkLeg.Right, request.WeekDefinitionId, maxNetworkLevel, cancellationToken);

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

            // ✅ مرحله 1: محاسبه تعادل اولیه (قبل از اعمال سقف)
            // تعادل = کمترین مقدار بین چپ و راست
            // مثال: چپ=500، راست=600 → تعادل=500
            var totalBalances = Math.Min(leftTotal, rightTotal);

            // ✅ مرحله 2: محاسبه باقیمانده (قبل از سقف)
            // باقیمانده = اضافه‌ای که یک طرف دارد
            // مثال: چپ=500، راست=600، تعادل=500
            //   → باقی چپ = 500-500 = 0
            //   → باقی راست = 600-500 = 100 (می‌رود برای هفته بعد)
            var leftRemainder = leftTotal - totalBalances;
            var rightRemainder = rightTotal - totalBalances;

            // ✅ مرحله 3: اعمال سقف 300 (برای امتیاز نهایی)
            // از تعادل، فقط 300 از هر طرف حساب می‌شود
            // مثال: تعادل=500 → امتیاز=300
            //   از چپ: 300 حساب می‌شود، 200 فلش می‌شود
            //   از راست: 300 حساب می‌شود، 200 فلش می‌شود
            //   جمع فلش = 400 (از بین می‌رود)
            var cappedBalances = Math.Min(totalBalances, maxBalancesPerLeg);
            
            // ✅ مرحله 4: محاسبه فلش (از هر دو طرف)
            var flushedPerSide = totalBalances - cappedBalances; // 500-300=200
            var totalFlushed = flushedPerSide * 2; // 200×2=400 (از بین می‌رود)

            // ⚠️ توجه: تعادل زیرمجموعه در این مرحله محاسبه نمیشه
            // چون هنوز تمام تعادل‌ها محاسبه نشدن
            // بعد از ذخیره همه تعادل‌ها، در یک حلقه دوم محاسبه خواهد شد

            var balance = new NetworkWeeklyBalance
            {
                UserId = user.Id,
                WeekDefinitionId = request.WeekDefinitionId,
                PackageId = package.Id,
                
                // اطلاعات جدید
                LeftLegNewMembers = leftNewMembers,
                RightLegNewMembers = rightNewMembers,
                LeftLegCarryover = leftCarryover,
                RightLegCarryover = rightCarryover,
                
                // مجموع
                LeftLegTotal = leftTotal,
                RightLegTotal = rightTotal,
                TotalBalances = cappedBalances, // امتیاز نهایی بعد از اعمال سقف 300
                
                // باقیمانده برای هفته بعد (اضافه‌ای که یک طرف دارد)
                LeftLegRemainder = leftRemainder,
                RightLegRemainder = rightRemainder,
                
                // فلش (از دست رفته)
                FlushedPerSide = flushedPerSide,
                TotalFlushed = totalFlushed,
                
                // تعادل زیرمجموعه - فعلاً 0 (بعد از ذخیره همه تعادل‌ها محاسبه میشه)
                SubordinateBalances = 0,
                
                // فیلدهای قدیمی (deprecated) - برای سازگاری با کدهای قبلی
#pragma warning disable CS0618
                LeftLegBalances = leftTotal,
                RightLegBalances = rightTotal,
#pragma warning restore CS0618
                
                WeeklyPoolContribution = 0, // Pool در مرحله بعد محاسبه میشه
                CalculatedAt = calculatedAt,
                IsExpired = false
            };

            balancesList.Add(balance);
        }

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

        // ⭐ مرحله 2: محاسبه تعادل زیرمجموعه برای هر کاربر (تا 15 لول)
        // حالا که همه تعادل‌ها ذخیره شدن، می‌تونیم تعادل زیرمجموعه رو حساب کنیم
        var balancesDictionary = balancesList.ToDictionary(x => x.UserId);
        
        foreach (var balance in balancesList)
        {
            var subordinateBalances = await CalculateSubordinateBalancesAsync(
                balance.UserId,
                balancesDictionary,
                maxNetworkLevel,
                cancellationToken
            );

            balance.SubordinateBalances = subordinateBalances;
        }

        // ذخیره تعادل‌های زیرمجموعه
        _context.NetworkWeeklyBalances.UpdateRange(balancesList);
        await _context.SaveChangesAsync(cancellationToken);

        return balancesList.Count;
    }

    /// <summary>
    /// شناسه هفته قبل را از WeekDefinitionRepository می‌گیرد
    /// </summary>
    private long? GetPreviousWeekDefinitionId(long currentWeekDefinitionId)
    {
        var currentWeekNumber = _weekRepository.GetGregorianWeekNumber(currentWeekDefinitionId);
        if (currentWeekNumber == null) return null;
        
        var previousWeekNumber = _weekRepository.GetPreviousWeekNumber(currentWeekNumber);
        if (previousWeekNumber == null) return null;
        
        return _weekRepository.GetWeekDefinitionId(previousWeekNumber);
    }

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

        // شمارش تمام اعضای زیرمجموعه که در این هفته فعال شدند (تا maxLevel لول)
        var count = await CountNewMembersRecursive(userId, leg, startDateEndDate.Value.startDate, startDateEndDate.Value.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;

        // ⭐ بر اساس بازه تاریخی هفته، نه IsCurrentCycle
        // IsCurrentCycle فقط آخرین خرید را نشان می‌دهد — برای تاریخچه‌ای (هفته‌های گذشته)
        // باید از PackagePurchasedAt استفاده کرد که تاریخ دقیق خرید را نگه می‌دارد
        var hadCycleInWeek = await _context.ClubMembershipCycles
            .AnyAsync(x => x.UserId == child.Id
                && x.PackagePurchasedAt >= startDate
                && x.PackagePurchasedAt <= endDate,
                cancellationToken);

        if (hadCycleInWeek)
        {
            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>
    /// تبدیل شماره هفته به بازه تاریخی از WeekDefinitionRepository
    /// </summary>
    // private (DateTime startDate, DateTime endDate) GetWeekDateRange(string weekNumber)
    // {
    //     var dateRange = _weekRepository.GetWeekDateRange(weekNumber);
    //     if (dateRange.HasValue)
    //     {
    //         return dateRange.Value;
    //     }
    //     
    //     // Fallback: محاسبه دستی اگر در کش نبود
    //     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 jan1DayOfWeek = (int)jan1.DayOfWeek;
    //     var daysToFirstSaturday = jan1DayOfWeek == 6 ? 0 : (6 - jan1DayOfWeek + 7) % 7;
    //     var firstSaturday = jan1.AddDays(daysToFirstSaturday);
    //     
    //     var weekStart = firstSaturday.AddDays((week - 1) * 7);
    //     var weekEnd = weekStart.AddDays(6).AddHours(23).AddMinutes(59).AddSeconds(59);
    //
    //     return (weekStart, weekEnd);
    // }

    /// <summary>
    /// محاسبه مجموع تعادل‌های زیرمجموعه یک کاربر تا maxLevel لول پایین‌تر
    /// </summary>
    private async Task<int> CalculateSubordinateBalancesAsync(
        long userId,
        Dictionary<long, NetworkWeeklyBalance> allBalances,
        int maxLevel,
        CancellationToken cancellationToken)
    {
        // پیدا کردن همه زیرمجموعه‌ها تا maxLevel لول
        var subordinates = await GetSubordinatesRecursive(userId, 1, maxLevel, cancellationToken);

        // جمع تعادل‌های آنها
        var totalSubordinateBalances = 0;
        foreach (var subordinateId in subordinates)
        {
            if (allBalances.ContainsKey(subordinateId))
            {
                totalSubordinateBalances += allBalances[subordinateId].TotalBalances;
            }
        }

        return totalSubordinateBalances;
    }

    /// <summary>
    /// پیدا کردن بازگشتی زیرمجموعه‌ها تا maxLevel لول
    /// </summary>
    private async Task<List<long>> GetSubordinatesRecursive(
        long userId,
        int currentLevel,
        int maxLevel,
        CancellationToken cancellationToken)
    {
        // محدودیت عمق
        if (currentLevel > maxLevel)
        {
            return new List<long>();
        }

        var result = new List<long>();

        // پیدا کردن فرزندان مستقیم
        var children = await _context.Users
            .Where(x => x.NetworkParentId == userId)
            .Select(x => x.Id)
            .ToListAsync(cancellationToken);

        result.AddRange(children);

        // بازگشت برای هر فرزند
        foreach (var childId in children)
        {
            var grandChildren = await GetSubordinatesRecursive(childId, currentLevel + 1, maxLevel, cancellationToken);
            result.AddRange(grandChildren);
        }

        return result;
    }
}
