private async Task GetUserAwardStats(CommandContext ctx, DiscordMember member)
        {
            var userId = member.Id;
            var guild  = ctx.Guild;

            var mention = member.GetNicknameOrDisplayName();

            var userAwardStats = await _awardMessageRepo.GetAwardStatsForUser(userId);

            var sb = new StringBuilder();

            var embedBuilder = new DiscordEmbedBuilder()
                               .WithAuthor(mention, null, member.AvatarUrl)
                               .WithTitle("Cookie stats")
                               .WithColor(DiscordConstants.EmbedColor);

            if (userAwardStats.TotalAwardCount == 0)
            {
                embedBuilder = embedBuilder.WithDescription("No awarded messages");

                await ctx.RespondAsync(embedBuilder.Build());

                return;
            }

            sb.AppendLine($"Total cookies: {userAwardStats.TotalAwardCount}");
            sb.AppendLine($"Messages in cookie channel: {userAwardStats.AwardMessageCount}");

            sb.AppendLine();
            sb.AppendLine("Top messages");
            foreach (var awardedMessage in userAwardStats.TopAwardedMessages)
            {
                sb.Append($"{DiscordEmoji.FromUnicode(EmojiMap.Cookie).Name} **{awardedMessage.AwardCount}** ");

                var channelId = awardedMessage.OriginalChannelId;

                var messageChannel = await _cache.LoadFromCacheAsync(
                    string.Format(SharedCacheKeys.DiscordChannel, channelId),
                    async() => await _discordResolver.ResolveChannel(guild, channelId),
                    TimeSpan.FromSeconds(10));

                if (messageChannel == null)
                {
                    continue;
                }

                var messageResolveResult = await _discordResolver.TryResolveMessage(messageChannel, awardedMessage.OriginalMessageId);

                if (messageResolveResult.Resolved)
                {
                    var message = messageResolveResult.Result;

                    var shortenedMessage = message.Content.Length > _maxMessageLength
                        ? $"{message.Content.Substring(0, _maxMessageLength)}..."
                        : message.Content;

                    if (string.IsNullOrWhiteSpace(shortenedMessage))
                    {
                        shortenedMessage = "*no message text*";
                    }

                    sb.AppendLine($"[{shortenedMessage}]({message.JumpLink})");
                }
                else
                {
                    sb.AppendLine("Message not found");
                }
            }

            embedBuilder = embedBuilder.WithDescription(sb.ToString());

            await ctx.RespondAsync(embedBuilder.Build());
        }
        public async Task HandleAwardChange(MessageAwardQueueItem awardItem)
        {
            // TODO optimize by resolving full message only if it will be posted
            var partialMessage = awardItem.DiscordMessage;

            var message = await _discordResolver.ResolveMessage(partialMessage.Channel, partialMessage.Id);

            if (message == null)
            {
                _logger.LogDebug("Could not resolve message");
                return;
            }

            var channel = message.Channel;
            var guild   = channel.Guild;

            var messageAuthor = await _discordResolver.ResolveGuildMember(guild, message.Author.Id);

            if (messageAuthor == null)
            {
                _logger.LogDebug("Could not resolve message author");
                return;
            }

            var awardChannel = await _discordResolver.ResolveChannel(guild, _options.AwardChannelId);

            if (awardChannel == null)
            {
                _logger.LogDebug("Could not resolve awards channel");
                return;
            }

            uint awardReactionCount = await GetAwardReactionCount(message, messageAuthor);

            var hasEnoughAwards = awardReactionCount >= _options.RequiredAwardCount;

            var awardMessage = await _awardMessageRepo.GetAwardMessageByOriginalMessageId(awardChannel.Id, message.Id);

            _logger.LogDebug($"Message has {awardReactionCount} awards");

            if (awardMessage == null)
            {
                _logger.LogDebug($"Message ({message.Id}) does not exist in the database");

                if (!hasEnoughAwards)
                {
                    _logger.LogDebug($"Not enough awards. {awardReactionCount} / {_options.RequiredAwardCount}");
                    return;
                }

                var postedMessage = await PostAwardedMessage(awardChannel, message, messageAuthor, awardReactionCount);

                await _awardMessageRepo.CreateAwardMessage(message.Id, message.ChannelId, postedMessage.Id, awardChannel.Id, messageAuthor.Id, awardReactionCount);
            }
            else
            {
                _logger.LogDebug($"Message ({message.Id}) exists in award channel");

                // TODO keep track if message was removed from award channels
                // so it's handled gracefully i.e. not throw an error
                // when it tries to update a removed message
                await UpdateAwardedMessageText(awardChannel, awardMessage.AwardedMessageId, awardReactionCount);

                await _awardMessageRepo.UpdateAwardCount(awardMessage.Id, awardReactionCount);
            }
        }