Introduce '/review_last' command support

Implemented support for the '/review_last' command within comments. If a
comment within a Change targets ChatGPT and contains this command, it
initiates a review of the Change's most recent PatchSet.

Jira-Id: IT-103
Change-Id: I3d03c75bf1ae3b8158323bb392593ccf6b23ed91
Signed-off-by: Patrizio <patrizio.gelosi@amarulasolutions.com>
diff --git a/README.md b/README.md
index e2ed963..8f94326 100644
--- a/README.md
+++ b/README.md
@@ -161,6 +161,11 @@
 
 - `isEnabled`: The default is false. If set to true, the plugin will review the Patch Set of this project.
 
+## Commands
+
+The `/review_last` command, when used in a comment directed at ChatGPT on any Change, triggers a review of the last
+Patch Set of the Change.
+
 ## Testing
 
 - You can run the unit tests in the project to familiarize yourself with the plugin's source code.
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClient.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClient.java
index b710afc..6317e79 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClient.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClient.java
@@ -54,6 +54,11 @@
         return gerritClientPatchSet.getPatchSet(fullChangeId, isCommentEvent);
     }
 
+    public boolean getForcedReview(String fullChangeId) {
+        updateGerritClient(GerritClientType.COMMENTS, fullChangeId);
+        return gerritClientComments.getForcedReview();
+    }
+
     public boolean isDisabledUser(String authorUsername) {
         return gerritClientPatchSet.isDisabledUser(authorUsername);
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClientComments.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClientComments.java
index 772d598..2adadbe 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClientComments.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/client/gerrit/GerritClientComments.java
@@ -26,6 +26,7 @@
 public class GerritClientComments extends GerritClientAccount {
     private static final String ROLE_USER = "user";
     private static final String ROLE_ASSISTANT = "assistant";
+    private static final Pattern REVIEW_LAST_COMMAND_PATTERN = Pattern.compile("/review_last\\b");
     private static final Integer MAX_SECS_GAP_BETWEEN_EVENT_AND_COMMENT = 2;
 
     private final Gson gson = new Gson();
@@ -36,6 +37,8 @@
     private String authorUsername;
     @Getter
     private List<GerritComment> commentProperties;
+    @Getter
+    private Boolean forcedReview;
 
     public GerritClientComments(Configuration config) {
         super(config);
@@ -47,6 +50,7 @@
 
     public boolean retrieveLastComments(Event event, String fullChangeId) {
         commentsStartTimestamp = event.eventCreatedOn;
+        forcedReview = false;
         CommentAddedEvent commentAddedEvent = (CommentAddedEvent) event;
         authorUsername = commentAddedEvent.author.get().username;
         log.debug("Found comments by '{}' on {}", authorUsername, commentsStartTimestamp);
@@ -127,6 +131,11 @@
         return true;
     }
 
+    private boolean isForcingReview(String comment) {
+        Matcher reviewCommandMatcher = REVIEW_LAST_COMMAND_PATTERN.matcher(comment);
+        return reviewCommandMatcher.find();
+    }
+
     private void addAllComments(String fullChangeId) {
         try {
             List<GerritComment> latestComments = getLastComments(fullChangeId);
@@ -135,6 +144,12 @@
             }
             for (GerritComment latestComment : latestComments) {
                 String commentMessage = latestComment.getMessage();
+                if (isForcingReview(commentMessage)) {
+                    log.debug("Forced review command detected in message {}", commentMessage);
+                    forcedReview = true;
+                    commentProperties.clear();
+                    return;
+                }
                 if (isBotAddressed(commentMessage)) {
                     commentProperties.add(latestComment);
                 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventListenerHandler.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventListenerHandler.java
index 22efacd..b360ff4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventListenerHandler.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/listener/EventListenerHandler.java
@@ -20,6 +20,7 @@
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.*;
 
@@ -27,6 +28,10 @@
 
 @Slf4j
 public class EventListenerHandler {
+    private final static Map<String, Boolean> EVENT_COMMENT_MAP = Map.of(
+            "patchset-created", false,
+            "comment-added", true
+    );
 
     private final PatchSetReviewer reviewer;
     private final BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
@@ -166,28 +171,34 @@
     private boolean preprocessEvent(Event event, String fullChangeId, Project.NameKey projectNameKey) {
         String eventType = Optional.ofNullable(event.getType()).orElse("");
         log.info("Event type {}", eventType);
+        if (!EVENT_COMMENT_MAP.containsKey(eventType) ) {
+            return false;
+        }
         PatchSetEvent patchSetEvent = (PatchSetEvent) event;
 
         if (!isReviewEnabled(patchSetEvent, projectNameKey)) {
             return false;
         }
-        switch (eventType) {
-            case "patchset-created":
-                if (!isPatchSetReviewEnabled(patchSetEvent)) {
-                    return false;
+        boolean isCommentEvent = EVENT_COMMENT_MAP.get(eventType);
+        if (isCommentEvent) {
+            if (!gerritClient.retrieveLastComments(event, fullChangeId)) {
+                if (gerritClient.getForcedReview(fullChangeId)) {
+                    isCommentEvent = false;
                 }
-                reviewer.setCommentEvent(false);
-                break;
-            case "comment-added":
-                if (!gerritClient.retrieveLastComments(event, fullChangeId)) {
+                else {
                     log.info("No comments found for review");
                     return false;
                 }
-                reviewer.setCommentEvent(true);
-                break;
-            default:
-                return false;
+            }
         }
+        else {
+            if (!isPatchSetReviewEnabled(patchSetEvent)) {
+                log.debug("Patch Set review disabled");
+                return false;
+            }
+        }
+        log.debug("Flag `isCommentEvent` set to {}", isCommentEvent);
+        reviewer.setCommentEvent(isCommentEvent);
 
         return true;
     }