namespace CMSMicroservice.Application.CommissionCQ.Commands.CalculateWeeklyCommissionPool;

public class CalculateWeeklyCommissionPoolCommandHandler : IRequestHandler<CalculateWeeklyCommissionPoolCommand, long>
{
    private readonly IApplicationDbContext _context;
    private readonly IWeekDefinitionRepository _weekRepository;

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

    public async Task<long> Handle(CalculateWeeklyCommissionPoolCommand request, CancellationToken cancellationToken)
    {
        // تبدیل WeekNumber به WeekDefinitionId
        var weekDefinitionId  = _weekRepository.GetGregorianWeekNumber(request.WeekDefinitionId);
        if (weekDefinitionId == null)
        {
            throw new InvalidOperationException($"هفته {request.WeekDefinitionId} در سیستم تعریف نشده است");
        }
        
        // بررسی وجود استخر
        var existingPoolQuery = _context.WeeklyCommissionPools
            .Where(x => x.WeekDefinitionId == request.WeekDefinitionId);

        if (request.PackageId.HasValue)
        {
            existingPoolQuery = existingPoolQuery.Where(x => x.PackageId == request.PackageId.Value);
        }

        var existingPool = await existingPoolQuery
            .OrderBy(x => x.Id)
            .FirstOrDefaultAsync(cancellationToken);

        if (existingPool == null)
        {
            throw new InvalidOperationException(
                $"Pool هفته {request.WeekDefinitionId} وجود ندارد. " +
                "Pool باید در هنگام فعالسازی باشگاه مشتریان ایجاد شده باشد"
            );
        }

        if (existingPool.IsCalculated && !request.ForceRecalculate)
        {
            throw new InvalidOperationException($"استخر کمیسیون هفته {request.WeekDefinitionId} قبلاً محاسبه شده است");
        }

        // بررسی وجود تعادل‌های هفتگی
        var weeklyBalancesQuery = _context.NetworkWeeklyBalances
            .Where(x => x.WeekDefinitionId == request.WeekDefinitionId);

        if (request.PackageId.HasValue)
        {
            weeklyBalancesQuery = weeklyBalancesQuery.Where(x => x.PackageId == request.PackageId.Value);
        }

        var weeklyBalances = await weeklyBalancesQuery.ToListAsync(cancellationToken);

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

        // ⭐ Pool از قبل پُر شده (توسط ActivateClubMembership)
        var totalPoolAmount = existingPool.TotalPoolAmount;

        // محاسبه مجموع تعادل‌های کل شبکه
        // نکته: SubordinateBalances اضافه نمی‌کنیم چون وقتی همه TotalBalances رو جمع می‌زنیم،
        // خودش شامل بالانس‌های زیرمجموعه‌ها هم هست (تکراری نشه)
        var totalBalancesInNetwork = weeklyBalances.Sum(x => x.TotalBalances);

        // محاسبه ارزش هر امتیاز
        long valuePerBalance = 0;
        if (totalBalancesInNetwork > 0)
        {
            valuePerBalance = totalPoolAmount / totalBalancesInNetwork;
        }

        // به‌روزرسانی Pool
        existingPool.TotalBalances = totalBalancesInNetwork;
        existingPool.ValuePerBalance = valuePerBalance;
        existingPool.IsCalculated = true;
        existingPool.CalculatedAt = DateTime.Now;

        _context.WeeklyCommissionPools.Update(existingPool);
        await _context.SaveChangesAsync(cancellationToken);

        // حذف پرداخت‌های قبلی در صورت ForceRecalculate
        if (request.ForceRecalculate)
        {
            var oldPayoutsQuery = _context.UserCommissionPayouts
                .Where(p => p.WeekDefinitionId == request.WeekDefinitionId);

            if (request.PackageId.HasValue)
            {
                oldPayoutsQuery = oldPayoutsQuery.Where(p => p.PackageId == request.PackageId.Value);
            }

            var oldPayouts = await oldPayoutsQuery.ToListAsync(cancellationToken);
            
            if (oldPayouts.Any())
            {
                var oldPayoutIds = oldPayouts.Select(p => p.Id).ToList();
                
                // ⭐ ۱. برگرداندن NetworkBalance به حالت قبل (Revert)
                await RevertUserWalletsAsync(oldPayoutIds, cancellationToken);
                
                // ⭐ ۲. حذف تاریخچه‌ها (به خاطر FK constraint)
                var oldHistories = await _context.CommissionPayoutHistories
                    .Where(h => oldPayoutIds.Contains(h.UserCommissionPayoutId))
                    .ToListAsync(cancellationToken);
                
                if (oldHistories.Any())
                {
                    _context.CommissionPayoutHistories.RemoveRange(oldHistories);
                }
                
                // ⭐ ۳. حذف پرداخت‌ها
                _context.UserCommissionPayouts.RemoveRange(oldPayouts);
                await _context.SaveChangesAsync(cancellationToken);
            }
        }

        // ⭐ ثبت پرداخت برای کاربرانی که تعادل دارند
        // تعادل شخصی + زیرمجموعه قبلاً در CalculateWeeklyBalances محاسبه شده
        var payouts = new List<UserCommissionPayout>();
        
        foreach (var balance in weeklyBalances)
        {
            // فقط تعادل شخصی (SubordinateBalances اضافه نمی‌شه چون در SUM کل شبکه خودش حساب میشه)
            var userBalance = balance.TotalBalances;

            // اگر تعادل صفر است، نیازی به ثبت نیست
            if (userBalance <= 0)
            {
                continue;
            }

            // محاسبه مبلغ کمیسیون
            var totalAmount = (long)(userBalance * valuePerBalance);

            var payout = new UserCommissionPayout
            {
                UserId = balance.UserId,
                WeekDefinitionId = request.WeekDefinitionId,
                WeeklyPoolId = existingPool.Id,
                BalancesEarned = userBalance,
                ValuePerBalance = valuePerBalance,
                TotalAmount = totalAmount,
                Status = CommissionPayoutStatus.Pending,
                PaidAt = null,
                WithdrawalMethod = null,
                IbanNumber = null,
                WithdrawnAt = null
            };

            payouts.Add(payout);
        }

        if (payouts.Any())
        {
            await _context.UserCommissionPayouts.AddRangeAsync(payouts, cancellationToken);
            await _context.SaveChangesAsync(cancellationToken);

            // ثبت تاریخچه برای هر پرداخت
            var historyList = new List<CommissionPayoutHistory>();
            foreach (var payout in payouts)
            {
                var history = new CommissionPayoutHistory
                {
                    UserCommissionPayoutId = payout.Id,
                    UserId = payout.UserId,
                    WeekDefinitionId = request.WeekDefinitionId,
                    AmountBefore = 0,
                    AmountAfter = payout.TotalAmount,
                    OldStatus = default(CommissionPayoutStatus),
                    NewStatus = CommissionPayoutStatus.Pending,
                    Action = CommissionPayoutAction.Created,
                    PerformedBy = "System",
                    Reason = "پردازش خودکار کمیسیون هفتگی"
                };

                historyList.Add(history);
            }

            await _context.CommissionPayoutHistories.AddRangeAsync(historyList, cancellationToken);
            await _context.SaveChangesAsync(cancellationToken);
            
            // ⭐ شارژ کیف پول کاربران (NetworkBalance) و ثبت لاگ
            await ChargeUserWalletsAsync(payouts, request.WeekDefinitionId, cancellationToken);
        }

        return existingPool.Id;
    }
    
    /// <summary>
    /// شارژ کیف پول کاربران با مبلغ کمیسیون (به NetworkBalance اضافه می‌شود)
    /// و ثبت لاگ تغییرات کیف پول
    /// </summary>
    private async Task ChargeUserWalletsAsync(
        List<UserCommissionPayout> payouts, 
        long weekDefinitionId,
        CancellationToken cancellationToken)
    {
        var userIds = payouts.Select(p => p.UserId).Distinct().ToList();
        
        // دریافت کیف پول‌های موجود
        var existingWallets = await _context.UserWallets
            .Where(w => userIds.Contains(w.UserId))
            .ToDictionaryAsync(w => w.UserId, cancellationToken);
        
        var newWallets = new List<UserWallet>();
        var walletLogs = new List<UserWalletHistory>();
        
        foreach (var payout in payouts)
        {
            if (payout.TotalAmount <= 0) continue;
            
            UserWallet wallet;
            
            // پیدا کردن یا ایجاد کیف پول
            if (existingWallets.TryGetValue(payout.UserId, out var existingWallet))
            {
                wallet = existingWallet;
            }
            else
            {
                // ایجاد کیف پول جدید
                wallet = new UserWallet
                {
                    UserId = payout.UserId,
                    Balance = 0,
                    NetworkBalance = 0,
                    DiscountBalance = 0
                };
                newWallets.Add(wallet);
                existingWallets[payout.UserId] = wallet; // برای جلوگیری از تکرار
            }
        }
        
        // ذخیره کیف پول‌های جدید
        if (newWallets.Any())
        {
            await _context.UserWallets.AddRangeAsync(newWallets, cancellationToken);
            await _context.SaveChangesAsync(cancellationToken);
        }
        
        // شارژ کیف پول‌ها و ثبت لاگ
        foreach (var payout in payouts)
        {
            if (payout.TotalAmount <= 0) continue;
            
            var wallet = existingWallets[payout.UserId];
            
            // ذخیره موجودی قبلی برای لاگ
            var previousNetworkBalance = wallet.NetworkBalance;
            
            // شارژ NetworkBalance
            wallet.NetworkBalance += payout.TotalAmount;
            
            // ایجاد لاگ تغییر کیف پول
            var walletLog = new UserWalletHistory
            {
                WalletId = wallet.Id,
                CurrentBalance = wallet.Balance,
                ChangeValue = 0, // Balance اصلی تغییر نمی‌کند
                CurrentNetworkBalance = wallet.NetworkBalance,
                ChangeNerworkValue = payout.TotalAmount,
                CurrentDiscountBalance = wallet.DiscountBalance,
                ChangeDiscountValue = 0,
                IsIncrease = true,
                RefrenceId = payout.Id // ارجاع به UserCommissionPayout
            };
            
            walletLogs.Add(walletLog);
        }
        
        // ذخیره تغییرات کیف پول و لاگ‌ها
        await _context.UserWalletHistories.AddRangeAsync(walletLogs, cancellationToken);
        await _context.SaveChangesAsync(cancellationToken);
    }
    
    /// <summary>
    /// برگرداندن NetworkBalance به حالت قبل از شارژ کمیسیون
    /// این متد قبل از محاسبه مجدد (ForceRecalculate) اجرا می‌شود
    /// </summary>
    private async Task RevertUserWalletsAsync(
        List<long> oldPayoutIds, 
        CancellationToken cancellationToken)
    {
        // پیدا کردن لاگ‌های کیف پول مرتبط با پرداخت‌های قبلی
        var oldWalletLogs = await _context.UserWalletHistories
            .Where(l => l.RefrenceId.HasValue && oldPayoutIds.Contains(l.RefrenceId.Value))
            .ToListAsync(cancellationToken);
        
        if (!oldWalletLogs.Any()) return;
        
        // پیدا کردن کیف پول‌های مرتبط
        var walletIds = oldWalletLogs.Select(l => l.WalletId).Distinct().ToList();
        var wallets = await _context.UserWallets
            .Where(w => walletIds.Contains(w.Id))
            .ToDictionaryAsync(w => w.Id, cancellationToken);
        
        // برگرداندن NetworkBalance
        foreach (var log in oldWalletLogs)
        {
            if (wallets.TryGetValue(log.WalletId, out var wallet))
            {
                // کم کردن مبلغ شارژ شده قبلی از NetworkBalance
                wallet.NetworkBalance -= log.ChangeNerworkValue;
                
                // اطمینان از منفی نشدن
                if (wallet.NetworkBalance < 0)
                {
                    wallet.NetworkBalance = 0;
                }
            }
        }
        
        // حذف لاگ‌های قبلی
        _context.UserWalletHistories.RemoveRange(oldWalletLogs);
        
        await _context.SaveChangesAsync(cancellationToken);
    }
}
