using System.Collections.Generic;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Common;
using CMSMicroservice.Domain.Entities.Club;
using CMSMicroservice.Domain.Entities.Commission;
using CMSMicroservice.Domain.Entities.Network;
using CMSMicroservice.Domain.Enums;
using Microsoft.EntityFrameworkCore;

namespace CMSMicroservice.Infrastructure.Services.Commission;

/// <summary>
/// پیاده‌سازی محاسبه کمیسیون با استفاده از Entity Framework (ORM)
/// این روش برای داده‌های کم مناسب است و دیباگ و تست راحت‌تری دارد
/// </summary>
public class OrmCommissionCalculationStrategy : ICommissionCalculationStrategy
{
    private readonly IApplicationDbContext _context;
    private readonly IWeekDefinitionRepository _weekRepository;

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

    /// <inheritdoc />
    public async Task<int> CalculateWeeklyBalancesAsync(
        long weekDefinitionId,
        bool forceRecalculate,
        long? packageId = null,
        CancellationToken cancellationToken = default)
    {
        var regorianWeekNumber = _weekRepository.GetGregorianWeekNumber(weekDefinitionId);
        if (regorianWeekNumber == null)
        {
            throw new InvalidOperationException($"هفته {weekDefinitionId} در سیستم تعریف نشده است");
        }

        // بررسی وجود محاسبه قبلی
        var existingBalances = await _context.NetworkWeeklyBalances
            .Where(x => x.WeekDefinitionId == weekDefinitionId)
            .ToListAsync(cancellationToken);

        if (existingBalances.Any() && !forceRecalculate)
        {
            throw new InvalidOperationException($"تعادل‌های هفته {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);

        // دریافت عضویت‌های فعال به همراه PackageId هر کاربر
        var activeClubMemberships = await _context.ClubMemberships
            .Where(c => c.IsActive && !magicModeUserIds.Contains(c.UserId))
            .Select(c => new { c.UserId, PackageId = c.LastPackageId })
            .ToListAsync(cancellationToken);

        var activeClubMemberUserIds = activeClubMemberships
            .Select(c => c.UserId)
            .ToHashSet();

        // نگاشت کاربر → PackageId
        var userPackageMap = activeClubMemberships
            .Where(c => c.PackageId.HasValue)
            .ToDictionary(c => c.UserId, c => c.PackageId!.Value);

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

        // بارگذاری همه پکیج‌های فعال (bulk load)
        var allPackages = await _context.Packages
            .Where(p => !p.IsDeleted)
            .ToDictionaryAsync(p => p.Id, cancellationToken);

        // پکیج پیش‌فرض (fallback برای کاربرانی که PackageId ندارند)
        var defaultPackage = allPackages.Values
            .FirstOrDefault(p => p.IsBasePackage)
            ?? throw new InvalidOperationException("پکیج پایه یافت نشد");

        // دریافت باقیمانده‌های هفته قبل — شامل PackageId
        var previousWeekDefinitionId = GetPreviousWeekDefinitionId(weekDefinitionId);
        Dictionary<(long UserId, long PackageId), (int LeftLegRemainder, int RightLegRemainder)> previousWeekCarryovers;

        if (previousWeekDefinitionId.HasValue)
        {
            previousWeekCarryovers = await _context.NetworkWeeklyBalances
                .Where(x => x.WeekDefinitionId == previousWeekDefinitionId.Value)
                .ToDictionaryAsync(
                    x => (x.UserId, x.PackageId),
                    x => (x.LeftLegRemainder, x.RightLegRemainder),
                    cancellationToken);
        }
        else
        {
            previousWeekCarryovers = new Dictionary<(long, long), (int, int)>();
        }

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

        foreach (var user in usersInNetwork.OrderBy(o => o.Id))
        {
            // تعیین پکیج هر کاربر (per-user)
            var userPackageId = userPackageMap.GetValueOrDefault(user.Id, defaultPackage.Id);
            var package = allPackages.GetValueOrDefault(userPackageId, defaultPackage);
            var maxBalancesPerLeg = package.MaxBalancesPerLeg;
            var maxNetworkLevel = package.MaxNetworkLevel;
            // دریافت باقیمانده هفته قبل — فیلتر per-package
            var leftCarryover = 0;
            var rightCarryover = 0;
            var carryoverKey = (UserId: user.Id, PackageId: userPackageId);
            if (previousWeekCarryovers.ContainsKey(carryoverKey))
            {
                leftCarryover = previousWeekCarryovers[carryoverKey].LeftLegRemainder;
                rightCarryover = previousWeekCarryovers[carryoverKey].RightLegRemainder;
            }

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

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

            // تعادل = کمترین مقدار بین چپ و راست
            var totalBalances = Math.Min(leftTotal, rightTotal);

            // باقیمانده برای هفته بعد
            var leftRemainder = leftTotal - totalBalances;
            var rightRemainder = rightTotal - totalBalances;

            // اعمال سقف 300
            var cappedBalances = Math.Min(totalBalances, maxBalancesPerLeg);

            // محاسبه فلش
            var flushedPerSide = totalBalances - cappedBalances;
            var totalFlushed = flushedPerSide * 2;

            var balance = new NetworkWeeklyBalance
            {
                UserId = user.Id,
                WeekDefinitionId = weekDefinitionId,
                PackageId = package.Id,
                LeftLegNewMembers = leftNewMembers,
                RightLegNewMembers = rightNewMembers,
                LeftLegCarryover = leftCarryover,
                RightLegCarryover = rightCarryover,
                LeftLegTotal = leftTotal,
                RightLegTotal = rightTotal,
                TotalBalances = cappedBalances,
                LeftLegRemainder = leftRemainder,
                RightLegRemainder = rightRemainder,
                FlushedPerSide = flushedPerSide,
                TotalFlushed = totalFlushed,
                SubordinateBalances = 0,
#pragma warning disable CS0618
                LeftLegBalances = leftTotal,
                RightLegBalances = rightTotal,
#pragma warning restore CS0618
                WeeklyPoolContribution = 0,
                CalculatedAt = calculatedAt,
                IsExpired = false
            };

            balancesList.Add(balance);
        }

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

        // محاسبه تعادل زیرمجموعه — per-user maxNetworkLevel
        var balancesDictionary = balancesList.ToDictionary(x => x.UserId);

        foreach (var balance in balancesList)
        {
            var balancePackage = allPackages.GetValueOrDefault(balance.PackageId, defaultPackage);
            var subordinateBalances = await CalculateSubordinateBalancesAsync(
                balance.UserId,
                balancesDictionary,
                balancePackage.MaxNetworkLevel,
                cancellationToken
            );

            balance.SubordinateBalances = subordinateBalances;
        }

        _context.NetworkWeeklyBalances.UpdateRange(balancesList);
        await _context.SaveChangesAsync(cancellationToken);

        return balancesList.Count;
    }

    /// <inheritdoc />
    public async Task<long> CalculateWeeklyCommissionPoolAsync(
        long weekDefinitionId,
        bool forceRecalculate,
        long? packageId = null,
        CancellationToken cancellationToken = default)
    {
        var weekDefinition = _weekRepository.GetGregorianWeekNumber(weekDefinitionId);
        if (weekDefinition == null)
        {
            throw new InvalidOperationException($"هفته {weekDefinitionId} در سیستم تعریف نشده است");
        }

        // بررسی وجود استخر
        var existingPoolQuery = _context.WeeklyCommissionPools
            .Where(x => x.WeekDefinitionId == weekDefinitionId);

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

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

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

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

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

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

        var weeklyBalances = await weeklyBalancesQuery.ToListAsync(cancellationToken);

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

        var totalPoolAmount = existingPool.TotalPoolAmount;
        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 (forceRecalculate)
        {
            var oldPayoutsQuery = _context.UserCommissionPayouts
                .Where(p => p.WeekDefinitionId == weekDefinitionId);

            if (packageId.HasValue)
            {
                oldPayoutsQuery = oldPayoutsQuery.Where(p => p.PackageId == 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);

                // حذف تاریخچه‌ها
                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);
            }
        }

        // ثبت پرداخت برای کاربرانی که تعادل دارند
        var payouts = new List<UserCommissionPayout>();

        foreach (var balance in weeklyBalances)
        {
            var userBalance = balance.TotalBalances;
            if (userBalance <= 0) continue;

            var totalAmount = (long)(userBalance * valuePerBalance);

            var payout = new UserCommissionPayout
            {
                UserId = balance.UserId,
                WeekDefinitionId = weekDefinitionId,
                WeeklyPoolId = existingPool.Id,
                PackageId = balance.PackageId, // per-user PackageId از محاسبه تعادل
                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 = payouts.Select(payout => new CommissionPayoutHistory
            {
                UserCommissionPayoutId = payout.Id,
                UserId = payout.UserId,
                WeekDefinitionId = weekDefinitionId,
                AmountBefore = 0,
                AmountAfter = payout.TotalAmount,
                OldStatus = default(CommissionPayoutStatus),
                NewStatus = CommissionPayoutStatus.Pending,
                Action = CommissionPayoutAction.Created,
                PerformedBy = "System",
                Reason = "پردازش خودکار کمیسیون هفتگی (ORM)"
            }).ToList();

            await _context.CommissionPayoutHistories.AddRangeAsync(historyList, cancellationToken);
            await _context.SaveChangesAsync(cancellationToken);

            // شارژ کیف پول کاربران
            await ChargeUserWalletsAsync(payouts, weekDefinitionId, cancellationToken);
        }

        return existingPool.Id;
    }

    #region Private Methods

    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);
    }

    private async Task<int> CountNewMembersInLeg(long userId, NetworkLeg leg, long weekDefinitionId, int maxLevel, CancellationToken cancellationToken)
    {
        var startDateEndDate = _weekRepository.GetWeekDateRange(weekDefinitionId);
        var count = await CountNewMembersRecursive(userId, leg, startDateEndDate.Value.startDate, startDateEndDate.Value.endDate, 0, maxLevel, cancellationToken);
        return count;
    }

    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;
        // ⚠️ از ClubMembershipCycle.PackagePurchasedAt استفاده میکنیم (نه ActivatedAt که overwrite میشد)
        var currentCycle = await _context.ClubMembershipCycles
            .FirstOrDefaultAsync(x => x.UserId == child.Id && x.IsCurrentCycle, cancellationToken);

        if (currentCycle?.PackagePurchasedAt >= startDate && currentCycle?.PackagePurchasedAt <= 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;
    }

    private async Task<int> CalculateSubordinateBalancesAsync(
        long userId,
        Dictionary<long, NetworkWeeklyBalance> allBalances,
        int maxLevel,
        CancellationToken cancellationToken)
    {
        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;
    }

    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;
    }

    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];
            wallet.NetworkBalance += payout.TotalAmount;

            var walletLog = new UserWalletHistory
            {
                WalletId = wallet.Id,
                CurrentBalance = wallet.Balance,
                ChangeValue = 0,
                CurrentNetworkBalance = wallet.NetworkBalance,
                ChangeNerworkValue = payout.TotalAmount,
                CurrentDiscountBalance = wallet.DiscountBalance,
                ChangeDiscountValue = 0,
                IsIncrease = true,
                RefrenceId = payout.Id
            };

            walletLogs.Add(walletLog);
        }

        await _context.UserWalletHistories.AddRangeAsync(walletLogs, cancellationToken);
        await _context.SaveChangesAsync(cancellationToken);
    }

    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);

        foreach (var log in oldWalletLogs)
        {
            if (wallets.TryGetValue(log.WalletId, out var wallet))
            {
                wallet.NetworkBalance -= log.ChangeNerworkValue;
                if (wallet.NetworkBalance < 0)
                {
                    wallet.NetworkBalance = 0;
                }
            }
        }

        _context.UserWalletHistories.RemoveRange(oldWalletLogs);
        await _context.SaveChangesAsync(cancellationToken);
    }

    #endregion
}
