using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Common;
using CMSMicroservice.Domain.Entities;
using CMSMicroservice.Domain.Entities.Club;
using CMSMicroservice.Domain.Entities.Commission;
using CMSMicroservice.Domain.Entities.History;
using CMSMicroservice.Domain.Enums;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace CMSMicroservice.Application.ClubMembershipCQ.Commands.AcceptClubMembershipContract;

/// <summary>
/// Handler برای پذیرش قرارداد باشگاه مشتریان
/// این handler:
/// 1. کد OTP را تایید می‌کند
/// 2. قرارداد را در جدول UserContract ثبت می‌کند
/// 3. باشگاه مشتری را فعال می‌کند (IsActive = true)
/// 4. مبلغ را به Pool هفته جاری اضافه می‌کند
/// 5. ویژگی‌های باشگاه را به کاربر اعطا می‌کند
/// </summary>
public class AcceptClubMembershipContractCommandHandler 
    : IRequestHandler<AcceptClubMembershipContractCommand, AcceptClubMembershipContractResponseDto>
{
    private readonly IApplicationDbContext _context;
    private readonly IConfiguration _cfg;
    private readonly IHashService _hashService;
    private readonly ICurrentUserService _currentUser;
    private readonly IWeekDefinitionRepository _weekRepository;
    private readonly ILogger<AcceptClubMembershipContractCommandHandler> _logger;
    
    private const int MaxAttempts = 5;
    private const string OtpPurpose = "signClubContract";

    public AcceptClubMembershipContractCommandHandler(
        IApplicationDbContext context,
        IConfiguration cfg,
        IHashService hashService,
        ICurrentUserService currentUser,
        IWeekDefinitionRepository weekRepository,
        ILogger<AcceptClubMembershipContractCommandHandler> logger)
    {
        _context = context;
        _cfg = cfg;
        _hashService = hashService;
        _currentUser = currentUser;
        _weekRepository = weekRepository;
        _logger = logger;
    }

    public async Task<AcceptClubMembershipContractResponseDto> Handle(
        AcceptClubMembershipContractCommand request,
        CancellationToken cancellationToken)
    {
        // خواندن UserId از JWT (امن‌تر از دریافت از کلاینت)
        if (!long.TryParse(_currentUser.UserId, out var userId))
            return new AcceptClubMembershipContractResponseDto
            {
                Success = false,
                Message = "کاربر احراز هویت نشده است"
            };

        _logger.LogInformation(
            "Processing club membership contract for UserId: {UserId}",
            userId
        );

        // 1. دریافت کاربر
        var user = await _context.Users
            .Include(u => u.ClubMembership)
            .FirstOrDefaultAsync(u => u.Id == userId, cancellationToken);

        if (user == null)
        {
            _logger.LogWarning("User not found: {UserId}", userId);
            throw new NotFoundException(nameof(User), userId);
        }

        // 2. بررسی خرید پکیج
        if (user.PackagePurchaseMethod == PackagePurchaseMethod.None)
        {
            return new AcceptClubMembershipContractResponseDto
            {
                Success = false,
                Message = "برای امضای قرارداد باشگاه مشتریان ابتدا باید پکیج پایه را خریداری کنید"
            };
        }

        // 3. بررسی عدم فعال بودن قبلی باشگاه
        if (user.ClubMembership?.IsActive == true)
        {
            return new AcceptClubMembershipContractResponseDto
            {
                Success = false,
                Message = "شما قبلاً عضو باشگاه مشتریان شده‌اید"
            };
        }

        // 4. تایید OTP
        var otpResult = await VerifyOtpAsync(user.Mobile, request.OtpCode, cancellationToken);
        if (!otpResult.Success)
        {
            return new AcceptClubMembershipContractResponseDto
            {
                Success = false,
                Message = otpResult.Message
            };
        }

        // 5. ثبت قرارداد در جدول UserContract
        var contract = await _context.Contracts
            .FirstOrDefaultAsync(c => c.Type == ContractType.ClubMembership, cancellationToken);

        if (contract == null)
        {
            // اگر قرارداد وجود ندارد، یک قرارداد پیش‌فرض ایجاد کنید
            contract = new Contract
            {
                Title = "قرارداد باشگاه مشتریان",
                Description = "قوانین و مقررات باشگاه مشتریان کارابازار",
                HtmlContent = request.ContractHtml,
                Type = ContractType.ClubMembership
            };
            await _context.Contracts.AddAsync(contract, cancellationToken);
            await _context.SaveChangesAsync(cancellationToken);
        }

        var userContract = new UserContract
        {
            UserId = user.Id,
            ContractId = contract.Id,
            SignGuid = request.SignGuid,
            SignedPdfFile = request.ContractHtml
        };
        await _context.UserContracts.AddAsync(userContract, cancellationToken);

        // 6. بارگذاری پکیج موثر برای امضای قرارداد
        // اولویت:
        //   1) پکیج Cycle جاری (مسیر صحیح برای پرداخت دستی)
        //   2) آخرین خرید پکیج
        //   3) پکیج پایه
        var currentCyclePackageId = await _context.ClubMembershipCycles
            .Where(c => c.UserId == userId && c.IsCurrentCycle)
            .OrderByDescending(c => c.CycleNumber)
            .Select(c => (long?)c.PackageId)
            .FirstOrDefaultAsync(cancellationToken);

        var latestPurchase = await _context.UserPackagePurchases
            .Where(p => p.UserId == userId)
            .OrderByDescending(p => p.PurchasedAt)
            .FirstOrDefaultAsync(cancellationToken);

        Package? package = null;

        if (currentCyclePackageId.HasValue && currentCyclePackageId.Value > 0)
        {
            package = await _context.Packages
                .FirstOrDefaultAsync(p => p.Id == currentCyclePackageId.Value && !p.IsDeleted, cancellationToken);

            if (package != null)
            {
                _logger.LogInformation(
                    "AcceptContract package source: CurrentCycle (UserId={UserId}, PackageId={PackageId})",
                    userId,
                    package.Id);
            }
        }

        if (package == null && latestPurchase != null)
        {
            package = await _context.Packages
                .FirstOrDefaultAsync(p => p.Id == latestPurchase.PackageId && !p.IsDeleted, cancellationToken);

            if (package != null)
            {
                _logger.LogInformation(
                    "AcceptContract package source: LatestPurchase (UserId={UserId}, PackageId={PackageId})",
                    userId,
                    package.Id);
            }
        }

        if (package == null)
        {
            package = await _context.Packages
                .FirstOrDefaultAsync(p => p.IsBasePackage && !p.IsDeleted, cancellationToken);

            if (package != null)
            {
                _logger.LogInformation(
                    "AcceptContract package source: BasePackage (UserId={UserId}, PackageId={PackageId})",
                    userId,
                    package.Id);
            }
        }

        if (package == null)
            throw new NotFoundException("پکیج یافت نشد");

        long giftValue = package.ActivationFee;
        long activationFeeValue = package.ActivationFee;
        
        _logger.LogInformation(
            "Using Package {PackageId} ({PackageName}): GiftValue={GiftValue}, ActivationFee={ActivationFee}",
            package.Id, package.Title, giftValue, activationFeeValue
        );

        // 7. فعال‌سازی باشگاه مشتریان
        ClubMembership clubMembership;
        bool isNewMembership = user.ClubMembership == null;
        var activationDate = DateTime.Now;

        if (isNewMembership)
        {
            clubMembership = new ClubMembership
            {
                UserId = user.Id,
                IsActive = true,
                ActivatedAt = activationDate,
                FirstActivationDate = activationDate,
                FirstPackageId = package.Id,
                LastActivationDate = activationDate,
                LastPackageId = package.Id,
                InitialContribution = activationFeeValue,
                GiftValue = giftValue,
                TotalEarned = 0,
                PurchaseMethod = user.PackagePurchaseMethod
            };
            await _context.ClubMemberships.AddAsync(clubMembership, cancellationToken);
            
            _logger.LogInformation(
                "Created new club membership for UserId {UserId} via {Method}, GiftValue: {GiftValue}",
                user.Id,
                user.PackagePurchaseMethod,
                giftValue
            );
        }
        else
        {
            clubMembership = user.ClubMembership!;
            clubMembership.IsActive = true;
            clubMembership.ActivatedAt = activationDate;
            clubMembership.LastActivationDate = activationDate;
            clubMembership.LastPackageId = package.Id;
            clubMembership.PurchaseMethod = user.PackagePurchaseMethod;
            _context.ClubMemberships.Update(clubMembership);
            
            _logger.LogInformation(
                "Reactivated club membership for UserId {UserId}",
                user.Id
            );
        }

        await _context.SaveChangesAsync(cancellationToken);

        // 8. ثبت تاریخچه
        var history = new ClubMembershipHistory
        {
            ClubMembershipId = clubMembership.Id,
            UserId = clubMembership.UserId,
            OldIsActive = !isNewMembership && !user.ClubMembership!.IsActive,
            NewIsActive = true,
            Action = ClubMembershipAction.Activated,
            Reason = isNewMembership
                ? $"Initial activation via contract signing - {user.PackagePurchaseMethod}"
                : $"Reactivated via contract signing - {user.PackagePurchaseMethod}",
            PerformedBy = _currentUser.GetPerformedBy()
        };

        _context.ClubMembershipHistories.Add(history);
        await _context.SaveChangesAsync(cancellationToken);

        // 8.5. آپدیت PackagePurchasedAt سایکل جاری به زمان امضای قرارداد
        // دلیل: Pool در همین لحظه شارژ می‌شود — Validation باید به همین هفته اشاره کند
        // سناریوی cross-week: کاربر هفته ۱۸ پکیج خریده، هفته ۱۹ قرارداد امضا کرده
        //   بدون این Fix: Cycle.PackagePurchasedAt = هفته ۱۸ ولی Pool هفته ۱۹ → اختلاف
        //   با این Fix: Cycle.PackagePurchasedAt = هفته ۱۹ = Pool هفته ۱۹ → درست
        var currentCycle = await _context.ClubMembershipCycles
            .FirstOrDefaultAsync(c => c.UserId == userId && c.IsCurrentCycle, cancellationToken);

        if (currentCycle != null)
        {
            currentCycle.PackagePurchasedAt = activationDate;
            _context.ClubMembershipCycles.Update(currentCycle);
            await _context.SaveChangesAsync(cancellationToken);

            _logger.LogInformation(
                "Updated Cycle #{CycleNumber} PackagePurchasedAt to contract-signing time for UserId {UserId}",
                currentCycle.CycleNumber,
                userId
            );
        }
        else
        {
            // در صورتی که Cycle وجود ندارد (legacy یا مسیر مستقیم AcceptContract)
            // Cycle را همین‌جا بساز تا Validation درست کار کند
            _logger.LogWarning(
                "No IsCurrentCycle found for UserId {UserId} at AcceptContract — creating Cycle now",
                userId
            );

            var maxCycleNumber = await _context.ClubMembershipCycles
                .Where(c => c.ClubMembershipId == clubMembership.Id)
                .MaxAsync(c => (int?)c.CycleNumber, cancellationToken) ?? 0;

            var newCycle = new ClubMembershipCycle
            {
                UserId = userId,
                ClubMembershipId = clubMembership.Id,
                CycleNumber = maxCycleNumber + 1,
                PackagePurchasedAt = activationDate,
                PurchaseMethod = user.PackagePurchaseMethod,
                PackageAmount = package.Price,
                PackageId = package.Id,
                IsCurrentCycle = true
            };

            _context.ClubMembershipCycles.Add(newCycle);
            await _context.SaveChangesAsync(cancellationToken);
        }
        var currentWeekDefinitionId = GetCurrentWeekDefinitionId();
        var weeklyPool = await _context.WeeklyCommissionPools
            .FirstOrDefaultAsync(p => p.WeekDefinitionId == currentWeekDefinitionId && p.PackageId == package.Id, cancellationToken);

        if (weeklyPool == null)
        {
            weeklyPool = new WeeklyCommissionPool
            {
                WeekDefinitionId = currentWeekDefinitionId,
                PackageId = package.Id,
                TotalPoolAmount = activationFeeValue,
                TotalBalances = 0,
                ValuePerBalance = 0,
                IsCalculated = false,
                CalculatedAt = null
            };

            await _context.WeeklyCommissionPools.AddAsync(weeklyPool, cancellationToken);
            
            _logger.LogInformation(
                "Created new WeeklyCommissionPool for WeekDefinitionId={WeekDefinitionId} with initial amount: {Amount}",
                currentWeekDefinitionId,
                activationFeeValue
            );
        }
        else
        {
            weeklyPool.TotalPoolAmount += activationFeeValue;
            _context.WeeklyCommissionPools.Update(weeklyPool);
            
            _logger.LogInformation(
                "Added {Amount} to existing WeeklyCommissionPool for WeekDefinitionId={WeekDefinitionId}. New total: {NewTotal}",
                activationFeeValue,
                currentWeekDefinitionId,
                weeklyPool.TotalPoolAmount
            );
        }

        await _context.SaveChangesAsync(cancellationToken);

        // 10. اعمال ویژگی‌های پکیج — DIFF/تفاضل (Q20)
        await ApplyFeatureDiffAsync(user.Id, clubMembership.Id, package.Id, isNewMembership, activationDate, cancellationToken);

        _logger.LogInformation(
            "Club membership contract accepted and activated for UserId: {UserId}, ContractId: {ContractId}, MembershipId: {MembershipId}",
            request.UserId,
            userContract.Id,
            clubMembership.Id
        );

        return new AcceptClubMembershipContractResponseDto
        {
            Success = true,
            Message = "قرارداد باشگاه مشتریان با موفقیت ثبت شد و عضویت شما فعال گردید",
            ContractId = userContract.Id
        };
    }

    /// <summary>
    /// دریافت شناسه تعریف هفته جاری
    /// </summary>
    private long GetCurrentWeekDefinitionId()
    {
        var week = _weekRepository.GetCurrentWeek();
        if (week == null)
        {
            throw new InvalidOperationException("هفته جاری در سیستم تعریف نشده است");
        }
        return week.Id;
    }

    private async Task ApplyFeatureDiffAsync(
        long userId, long membershipId, long packageId,
        bool isNewMembership, DateTime activationDate,
        CancellationToken cancellationToken)
    {
        var newFeatureIds = await _context.PackageFeatures
            .Where(pf => pf.PackageId == packageId && pf.IsIncluded && !pf.IsDeleted)
            .Select(pf => pf.ClubFeatureId)
            .ToListAsync(cancellationToken);

        if (!newFeatureIds.Any())
        {
            _logger.LogWarning("No PackageFeatures for PackageId {PackageId}", packageId);
            return;
        }

        var currentFeatures = await _context.UserClubFeatures
            .Where(ucf => ucf.UserId == userId && ucf.IsActive)
            .ToListAsync(cancellationToken);

        var currentFeatureIds = currentFeatures.Select(f => f.ClubFeatureId).ToHashSet();
        var toAdd = newFeatureIds.Where(id => !currentFeatureIds.Contains(id)).ToList();
        var toRemove = currentFeatures.Where(f => !newFeatureIds.Contains(f.ClubFeatureId)).ToList();

        foreach (var feature in toRemove)
        {
            feature.IsActive = false;
            feature.Notes = $"حذف شده بابت تغییر پکیج (PackageId: {packageId})";
        }

        if (toAdd.Any())
        {
            var newUserFeatures = toAdd.Select(featureId => new UserClubFeature
            {
                UserId = userId,
                ClubMembershipId = membershipId,
                ClubFeatureId = featureId,
                GrantedAt = activationDate,
                IsActive = true,
                Notes = isNewMembership
                    ? "اعطا شده هنگام امضای قرارداد"
                    : $"اضافه شده بابت خرید مجدد پکیج (PackageId: {packageId})"
            }).ToList();

            _context.UserClubFeatures.AddRange(newUserFeatures);
        }

        await _context.SaveChangesAsync(cancellationToken);

        _logger.LogInformation(
            "Feature DIFF applied for UserId={UserId}: Added={Added}, Removed={Removed}, Kept={Kept}",
            userId, toAdd.Count, toRemove.Count,
            currentFeatureIds.Count - toRemove.Count);
    }

    private async Task<(bool Success, string Message)> VerifyOtpAsync(
        string mobile,
        string code,
        CancellationToken cancellationToken)
    {
        var normalizedMobile = mobile.NormalizeIranMobile();
        var now = DateTime.Now;

        var otp = await _context.OtpTokens
            .Where(o => o.Mobile == normalizedMobile 
                     && o.Purpose == OtpPurpose 
                     && !o.IsUsed 
                     && o.ExpiresAt > now)
            .OrderByDescending(o => o.Created)
            .FirstOrDefaultAsync(cancellationToken);

        if (otp == null)
        {
            return (false, "کد تایید پیدا نشد یا منقضی شده است");
        }

        if (otp.Attempts >= MaxAttempts)
        {
            return (false, "تعداد تلاش‌ها بیش از حد مجاز است. لطفاً کد جدید دریافت کنید");
        }

        otp.Attempts++;

        var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not configured");
        
        if (!_hashService.VerifyHmacSha256Hex(code, otp.CodeHash, secret))
        {
            await _context.SaveChangesAsync(cancellationToken);
            return (false, "کد تایید نادرست است");
        }

        // کد صحیح است - علامت‌گذاری به عنوان استفاده شده
        otp.IsUsed = true;
        await _context.SaveChangesAsync(cancellationToken);

        return (true, "کد تایید صحیح است");
    }
}
