public void Handle(DB4Thing privateMessage)
        {
            // The body should be the URL to a comment
            string commentUrl = privateMessage.Body.Trim();

            try
            {
                // Get comment by url
                var comment = _redditService.GetCommentByUrl(commentUrl);

                // If that succeeded, we need the full comment with children to check for replies
                _redditService.PopulateParentAndChildren(comment);

                // Check for replies
                var db4ReplyResult = _commentDetector.DidDB4Reply(comment);

                // If a the parent authorname is [deleted], bail
                if (comment.ParentThing.AuthorName == Constants.DeletedAuthorName)
                {
                    _redditService.ReplyToPrivateMessage(privateMessage.Id, DeleteFailedAuthorDeletedMessage);
                    return;
                }

                // If a delta was never awarded in the first place, bail
                if (!db4ReplyResult.HasDB4Replied || !db4ReplyResult.WasSuccessReply && db4ReplyResult.CommentType != DB4CommentType.ModeratorAdded)
                {
                    _redditService.ReplyToPrivateMessage(privateMessage.Id,
                                                         DeleteFailedNeverAwardedMessage);

                    return;
                }

                // A delta was awarded, unaward it
                _deltaAwarder.Unaward(comment);

                // Build moderator removal message
                var reply = _commentBuilder.BuildReply(DB4CommentType.ModeratorRemoved, comment);

                // Don't edit the success comment - delete it and reply with the mod deleted reply
                _replier.DeleteReply(db4ReplyResult.Comment);
                _replier.Reply(comment, reply);

                // Build modmail body
                string body = _appConfiguration.PrivateMessages.ModDeletedDeltaNotificationMessage
                              .Replace(_appConfiguration.ReplaceTokens.UsernameToken, privateMessage.AuthorName)
                              .Replace(_appConfiguration.ReplaceTokens.CommentLink, commentUrl);

                // Reply with modmail indicating success
                _redditService.SendPrivateMessage(_appConfiguration.PrivateMessages.ModDeletedDeltaNotificationSubject,
                                                  body, $"/r/{_appConfiguration.SubredditName}");

                // Reply to user
                _redditService.ReplyToPrivateMessage(privateMessage.Id, DeleteSucceededMessage);
            }
            catch (Exception ex)
            {
                // Reply indicating failure
                _redditService.ReplyToPrivateMessage(privateMessage.Id,
                                                     string.Format(DeleteFailedErrorMessageFormat, ex.ToString()));

                // Rethrow for logging purposes
                throw;
            }
        }
        public void Process(DB4Thing comment)
        {
            // If we got here with a PM or post, that's a problem
            Assert.That(comment.Type == DB4ThingType.Comment, $"CommentProcessor received type: {comment.Type}");

            _logger.Info($"Processing incoming comment{(comment.IsEdited ? " (edit)" : string.Empty)}: {comment.Permalink}");

            // DB4 shouldn't process its own comments
            if (comment.AuthorName == _appConfiguration.DB4Username)
            {
                _logger.Info($"Done processing comment{(comment.IsEdited ? " (edit)" : string.Empty)}.");
                return;
            }

            // Comments with ninja edits will need to be refreshed
            if (comment.NeedsRefresh)
            {
                // Get latest comment and make sure to treat this as an edit
                // so the potential unaward code path is processed
                comment          = _redditService.GetThingByFullname(comment.FullName);
                comment.IsEdited = true;
            }

            // Check for a delta
            bool hasDelta = commentHasDelta(comment.Body, out bool hadDeltaInQuotes);

            // If there was no legitimate delta, but there was a delta in quotes, PM
            // the user to let them know in case they made a mistake with reddit quoting
            if (!hasDelta && hadDeltaInQuotes && !_db4Repository.GetIgnoreQuotedDeltaPMUserList().Contains(comment.AuthorName))
            {
                string subject = _appConfiguration.PrivateMessages.DeltaInQuoteSubject;
                string body    = _appConfiguration.PrivateMessages.DeltaInQuoteMessage
                                 .Replace(_appConfiguration.ReplaceTokens.UsernameToken, comment.AuthorName)
                                 .Replace(_appConfiguration.ReplaceTokens.SubredditToken, _appConfiguration.SubredditName);

                _redditService.SendPrivateMessage(subject, body, comment.AuthorName);
            }

            if (hasDelta || comment.IsEdited)
            {
                // There is a delta or this comment is edited
                // We need to get more info for processing
                _redditService.PopulateParentAndChildren(comment);

                if (hasDelta)
                {
                    // Check to see if db4 has already replied
                    var db4ReplyResult = _commentDetector.DidDB4Reply(comment);

                    // If DB4 hasn't replied, or if it did but this is an edit, perform comment logic
                    if (!db4ReplyResult.HasDB4Replied)
                    {
                        // Validate comment and award delta if successful
                        var commentValidationResult = validateAndAward(comment);

                        // Post a reply with the result
                        _commentReplier.Reply(comment, commentValidationResult);
                    }
                    else
                    {
                        // DB4 already replied. If DB4's reply was a fail reply, check to see if this delta
                        // now passes validation (re-check edits). If it does, edit the old reply to be a success reply
                        // NOTE: Moderator replies (force add / remove) won't be processed
                        if (!db4ReplyResult.WasSuccessReply && !db4ReplyResult.WasModeratorReply)
                        {
                            // Validate comment and award delta if successful
                            var commentValidationResult = validateAndAward(comment);

                            // Edit the result to reflect new delta comment
                            _commentReplier.EditReply(db4ReplyResult.Comment, commentValidationResult);
                        }
                    }
                }
                else if (comment.IsEdited)
                {
                    // There is no delta. Check if DB4 replied. This means that
                    // there was a delta previously. If the comment is less than HoursToRemoveDelta hours old, the delta
                    // can be removed.

                    // Check to see if db4 has replied
                    var db4ReplyResult = _commentDetector.DidDB4Reply(comment);

                    // If DB4 replied and awarded a delta in the last HoursToUnawardDelta, unaward it
                    if (db4ReplyResult.HasDB4Replied && db4ReplyResult.WasSuccessReply && (DateTime.UtcNow - comment.CreatedUtc).TotalHours < _appConfiguration.HoursToUnawardDelta)
                    {
                        // Unaward
                        // parentThing can safely be cast to Comment here - we could have only
                        // gotten here if a delta was previously awarded, meaning the parent of this
                        // Comment is a Comment also
                        Assert.That(comment.ParentThing.Type == DB4ThingType.Comment);
                        _deltaAwarder.Unaward(comment);

                        // Delete award comment
                        _commentReplier.DeleteReply(db4ReplyResult.Comment);
                    }
                    else if (db4ReplyResult.HasDB4Replied && !db4ReplyResult.WasSuccessReply)
                    {
                        // DB4 replied and it was a failure reply. Now there's no delta.
                        // The user just removed the delta with their edit, so delete the reply
                        _commentReplier.DeleteReply(db4ReplyResult.Comment);
                    }
                }
            }

            _logger.Info($"Done processing comment{(comment.IsEdited ? " (edit)" : string.Empty)}.");
        }