using CMSMicroservice.Domain.Enums;
using CMSMicroservice.Domain.Events;
using Microsoft.Extensions.Configuration;

namespace CMSMicroservice.Application.OtpTokenCQ.Commands.VerifyOtpToken;

public class VerifyOtpTokenCommandHandler : IRequestHandler<VerifyOtpTokenCommand, VerifyOtpTokenResponseDto>
{
    private readonly IApplicationDbContext _context;
    private readonly IConfiguration _cfg;
    private readonly IHashService _hashService;

    public VerifyOtpTokenCommandHandler(IApplicationDbContext context, IConfiguration cfg, IHashService hashService)
    {
        _context = context;
        _cfg = cfg;
        _hashService = hashService;
    }

    const int MaxAttempts = 5; // محدودیت تلاش    

    public async Task<VerifyOtpTokenResponseDto> Handle(VerifyOtpTokenCommand request,
        CancellationToken cancellationToken)
    {
        var mobile = request.Mobile.NormalizeIranMobile();
        var purpose = request.Purpose?.ToLowerInvariant() ?? "signup";
        var now = DateTime.Now;

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

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

        if (otp.Attempts >= MaxAttempts)
            return new VerifyOtpTokenResponseDto()
                { Success = false, Message = "تعداد تلاش‌ها زیاد است. دوباره کد بگیرید." };

        otp.Attempts++;

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

        // موفق
        otp.IsUsed = true;

        // کاربر را بساز/به‌روزرسانی کن
        var user = await _context.Users.FirstOrDefaultAsync(u => u.Mobile == mobile, cancellationToken);

        if (user is null)
        {
            if (purpose == "login")
                return new VerifyOtpTokenResponseDto() { Success = false, Message = "کاربری با این شماره موبایل وجود ندارد." };

            if (request.ParentReferralCode == null)
                return new VerifyOtpTokenResponseDto() { Success = false, Message = "کد معرف الزامی است." };

            var parent = await _context.Users
                .Include(u => u.ClubMembership)
                .FirstOrDefaultAsync(u => u.ReferralCode == request.ParentReferralCode,
                    cancellationToken: cancellationToken);
            if (parent == null)
                return new VerifyOtpTokenResponseDto() { Success = false, Message = "معرف وجود ندارد." };

            // بررسی فعالیت باشگاه معرف
            if (parent.ClubMembership == null || !parent.ClubMembership.IsActive)
                return new VerifyOtpTokenResponseDto() 
                { 
                    Success = false, 
                    Message = "لینک دعوت معرف فعال نیست. لطفاً از کد دعوت معتبر دیگری استفاده کنید." 
                };

            if (await _context.Users.CountAsync(x => x.NetworkParentId == parent.Id, cancellationToken: cancellationToken) > 1)
                return new VerifyOtpTokenResponseDto() { Success = false, Message = "ظرفیت معرف تکمیل است!!" };

            // تعیین موقعیت در شبکه (چپ یا راست)
            var existingChildren = await _context.Users
                .Where(x => x.NetworkParentId == parent.Id && !x.IsDeleted)
                .Select(x => x.LegPosition)
                .ToListAsync(cancellationToken);

            NetworkLeg newUserLegPosition;
            if (!existingChildren.Any(x => x == NetworkLeg.Left))
            {
                newUserLegPosition = NetworkLeg.Left;
            }
            else if (!existingChildren.Any(x => x == NetworkLeg.Right))
            {
                newUserLegPosition = NetworkLeg.Right;
            }
            else
            {
                return new VerifyOtpTokenResponseDto() { Success = false, Message = "ظرفیت معرف تکمیل است!!" };
            }

            user = new User
            {
                Mobile = mobile,
                ReferralCode = UtilExtensions.Generate(digits: 10, firstDigitNonZero: true),
                IsMobileVerified = true,
                MobileVerifiedAt = now,
                IsRulesAccepted = true,
                RulesAcceptedAt = now,
                NetworkParentId = parent.Id,
                LegPosition = newUserLegPosition
            };
            await _context.Users.AddAsync(user, cancellationToken);
            user.AddDomainEvent(new CreateNewUserEvent(user));
            await _context.SaveChangesAsync(cancellationToken);
            
            var userRole = new UserRole
            {
                UserId = user.Id,
                RoleId = 1, //UserRoleEnum.User
            };
         
            await _context.UserRoles.AddAsync(userRole, cancellationToken);
            user.AddDomainEvent(new CreateNewUserRoleEvent(userRole));
            
            var userWallet = new UserWallet
            {
                UserId = user.Id,
                Balance = 0,
                NetworkBalance = 0
            };
            await _context.UserWallets.AddAsync(userWallet, cancellationToken);
            user.AddDomainEvent(new CreateNewUserWalletEvent(userWallet));
            await _context.SaveChangesAsync(cancellationToken);
        }
        else
        {
            user.IsMobileVerified = true;
            user.MobileVerifiedAt ??= now;

            _context.Users.Update(user);
            user.AddDomainEvent(new UpdateUserEvent(user));
            await _context.SaveChangesAsync(cancellationToken);
        }

        return new VerifyOtpTokenResponseDto()
        {
            Success = true,
            Message = "اعتبارسنجی موفق.",
            UserId = user.Id,
            RemainingAttempts = MaxAttempts,
            RemainingSeconds = (otp.ExpiresAt - now).Seconds
        };
    }
}