package com.rit.whatsapp.controller; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.rit.whatsapp.handler.GrievanceHandler; import com.rit.whatsapp.handler.LoginHandler; import com.rit.whatsapp.handler.StudentMenuHandler; import com.rit.whatsapp.model.UserState; import com.rit.whatsapp.service.SessionService; import com.rit.whatsapp.service.WhatsAppMessageService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/webhook") public class WebhookController { @Value("${whatsapp.verify-token:my_secure_verify_token}") private String verifyToken; private final WhatsAppMessageService messageService; private final SessionService sessionService; private final StudentMenuHandler studentMenuHandler; private final GrievanceHandler grievanceHandler; private final LoginHandler loginHandler; private final ObjectMapper objectMapper; @Autowired public WebhookController(WhatsAppMessageService messageService, SessionService sessionService, StudentMenuHandler studentMenuHandler, GrievanceHandler grievanceHandler, LoginHandler loginHandler) { this.messageService = messageService; this.sessionService = sessionService; this.studentMenuHandler = studentMenuHandler; this.grievanceHandler = grievanceHandler; this.loginHandler = loginHandler; this.objectMapper = new ObjectMapper(); } @GetMapping public ResponseEntity verifyWebhook( @RequestParam(name = "hub.mode", required = false) String mode, @RequestParam(name = "hub.verify_token", required = false) String token, @RequestParam(name = "hub.challenge", required = false) String challenge) { if ("subscribe".equals(mode) && verifyToken.equals(token)) { return ResponseEntity.ok(challenge); } else { return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); } } @PostMapping public ResponseEntity receiveMessage(@RequestBody String payload) { System.out.println("Received Webhook Payload: " + payload); try { JsonNode rootNode = objectMapper.readTree(payload); if (rootNode.has("entry") && rootNode.get("entry").isArray()) { JsonNode entryNode = rootNode.get("entry").get(0); if (entryNode.has("changes") && entryNode.get("changes").isArray()) { JsonNode changesNode = entryNode.get("changes").get(0); JsonNode valueNode = changesNode.get("value"); if (valueNode != null && valueNode.has("messages") && valueNode.get("messages").isArray()) { JsonNode messageNode = valueNode.get("messages").get(0); String fromPhoneNumber = messageNode.get("from").asText(); String messageType = messageNode.get("type").asText(); UserState currentState = sessionService.getUserState(fromPhoneNumber); boolean isAuthenticated = sessionService.isAuthenticated(fromPhoneNumber); // Global reset command if ("text".equals(messageType) && messageNode.get("text").get("body").asText().trim().equalsIgnoreCase("reset")) { sessionService.resetState(fromPhoneNumber); if (isAuthenticated) { studentMenuHandler.sendMainMenu(fromPhoneNumber); } else { loginHandler.startLoginFlow(fromPhoneNumber); } return ResponseEntity.ok("EVENT_RECEIVED"); } // Route unauthenticated users strictly to Login Flow if (!isAuthenticated) { if ("text".equals(messageType)) { String body = messageNode.get("text").get("body").asText(); handleUnauthenticatedUser(fromPhoneNumber, body, currentState); } else { // If they send a sticker/image/etc before logging in, force login handleUnauthenticatedUser(fromPhoneNumber, "", currentState); } return ResponseEntity.ok("EVENT_RECEIVED"); } // Authenticated users flow if ("text".equals(messageType)) { String messageBody = messageNode.get("text").get("body").asText(); handleAuthenticatedTextMessage(fromPhoneNumber, messageBody, currentState); } else if ("interactive".equals(messageType)) { JsonNode interactiveNode = messageNode.get("interactive"); String interactiveType = interactiveNode.get("type").asText(); if ("list_reply".equals(interactiveType)) { String selectedListId = interactiveNode.get("list_reply").get("id").asText(); handleInteractiveListSelection(fromPhoneNumber, selectedListId, currentState); } else if ("button_reply".equals(interactiveType)) { String selectedButtonId = interactiveNode.get("button_reply").get("id").asText(); handleInteractiveButtonSelection(fromPhoneNumber, selectedButtonId, currentState); } } } } } } catch (Exception e) { System.err.println("Error processing webhook payload: " + e.getMessage()); } return ResponseEntity.ok("EVENT_RECEIVED"); } private void handleUnauthenticatedUser(String phoneNumber, String messageBody, UserState currentState) { switch (currentState) { case AWAITING_LOGIN_ID: loginHandler.handleLoginIdInput(phoneNumber, messageBody); break; case AWAITING_LOGIN_PASSCODE: loginHandler.handlePasscodeInput(phoneNumber, messageBody); break; default: // Trigger the start of the login flow loginHandler.startLoginFlow(phoneNumber); break; } } private void handleAuthenticatedTextMessage(String phoneNumber, String messageBody, UserState currentState) { switch (currentState) { case AWAITING_GRIEVANCE_DESC: grievanceHandler.handleGrievanceDescription(phoneNumber, messageBody); break; case MAIN_MENU: default: // Fallback to main menu studentMenuHandler.sendMainMenu(phoneNumber); break; } } private void handleInteractiveListSelection(String phoneNumber, String selectedListId, UserState currentState) { if (selectedListId.startsWith("student_")) { // They clicked an option from the main menu studentMenuHandler.handleMenuSelection(phoneNumber, selectedListId); } else if (selectedListId.startsWith("sem_")) { // They clicked an option from the semester selection list if (currentState == UserState.AWAITING_SEMESTER_SELECTION) { studentMenuHandler.handleSemesterSelection(phoneNumber, selectedListId); } else { messageService.sendMessage(phoneNumber, "This menu selection is no longer active."); studentMenuHandler.sendMainMenu(phoneNumber); } } else if (selectedListId.startsWith("receipt_")) { // They clicked a receipt download option if (currentState == UserState.AWAITING_RECEIPT_SELECTION) { studentMenuHandler.handleReceiptSelection(phoneNumber, selectedListId); } else { messageService.sendMessage(phoneNumber, "This menu selection is no longer active."); studentMenuHandler.sendMainMenu(phoneNumber); } } else { studentMenuHandler.sendMainMenu(phoneNumber); } } private void handleInteractiveButtonSelection(String phoneNumber, String selectedButtonId, UserState currentState) { if ("student_logout".equals(selectedButtonId)) { sessionService.logoutUser(phoneNumber); messageService.sendMessage(phoneNumber, "You have been logged out successfully. Type 'Hi' to log in again."); return; } if ("student_weekly_timetable".equals(selectedButtonId)) { studentMenuHandler.handleWeeklyTimetable(phoneNumber); return; } if (currentState == UserState.AWAITING_GRIEVANCE_CATEGORY) { grievanceHandler.handleCategorySelection(phoneNumber, selectedButtonId); } else { studentMenuHandler.sendMainMenu(phoneNumber); } } }