MessageService.java

package de.sesqa.ase.services;

import de.sesqa.ase.api.ApiWrapper;
import de.sesqa.ase.dto.ChatMessageRequest;
import de.sesqa.ase.dto.ChatMessageResponse;
import de.sesqa.ase.entities.Conversation;
import de.sesqa.ase.entities.Message;
import de.sesqa.ase.metrics.CollectdClient;
import de.sesqa.ase.repositories.ConversationRepository;
import de.sesqa.ase.repositories.MessageRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Service class responsible for handling chat messages, managing conversations, persisting
 * messages, and interacting with external APIs and metrics systems.
 */
@Service
public class MessageService {

    /**
     * Logger instance for logging events and errors.
     */
    private static final Logger logger = LoggerFactory.getLogger(MessageService.class);

    /**
     * Repository for accessing and managing Message entities.
     */
    private final MessageRepository messageRepository;

    /**
     * Repository for accessing and managing Conversation entities.
     */
    private final ConversationRepository conversationRepository;

    /**
     * Client for sending metrics to Collectd.
     */
    private final CollectdClient collectdClient;


    // Add this field to your MessageService class
    private final AtomicLong totalApiCalls = new AtomicLong(0);

    /**
     * Constructs the MessageService with necessary repositories.
     *
     * @param messageRepository      Repository for message data access.
     * @param conversationRepository Repository for conversation data access.
     */
    public MessageService(
            MessageRepository messageRepository, ConversationRepository conversationRepository) {
        this.messageRepository = messageRepository;
        this.conversationRepository = conversationRepository;
        this.collectdClient = new CollectdClient();

        logger.info("MessageService initialized with repositories and metrics client.");
    }

    /**
     * Handles incoming chat messages from the user. It finds or creates a conversation, saves the
     * user message, queries an external API for a response, saves the bot's response, and returns it
     * to the client.
     *
     * @param request The chat message request from the client.
     * @return A {@link ChatMessageResponse} containing the bot's reply and the conversation ID.
     */
    public ChatMessageResponse handleMessage(@RequestBody ChatMessageRequest request) {
        long startTime = System.currentTimeMillis(); // --- Start Measurement ---

        if (request == null) {
            logger.error("Received null ChatMessageRequest");
            return new ChatMessageResponse("Request cannot be null.", null);
        }
        logger.info(
                """
                        Received chat message request:\s
                        
                        conversationId: {}
                        content: {}""",
                request.getConversationId(),
                request.getContent()
        );


        Long conversationId = request.getConversationId();
        try {
            Conversation conversation = getOrCreateConversation(conversationId);
            ChatMessageResponse response = processChatInteraction(conversation, request);

            // --- End Measurement & Send Metric on Success ---
            long duration = System.currentTimeMillis() - startTime;
            collectdClient.sendMetric("total_request_time", CollectdClient.CollectdType.GAUGE, duration);

            return response;
        } catch (Exception e) {
            logger.error("Error processing message", e);

            // --- End Measurement & Send Metric on Error ---
            long duration = System.currentTimeMillis() - startTime;
            collectdClient.sendMetric("total_request_time", CollectdClient.CollectdType.GAUGE, duration);

            return new ChatMessageResponse("Error processing message: " + e.getMessage(), conversationId);
        }
    }

    /**
     * Processes a full chat interaction consisting of a user message and the generated bot response.
     *
     * @param conversation A conversation between the user and the bot.
     * @param request      The chat message request from the client.
     * @return {@link ChatMessageResponse} containing the bot's reply and the conversation ID.
     * @throws InterruptedException if the thread is interrupted during processing.
     * @throws IOException          if an I/O error occurs during API interaction.
     */
    private ChatMessageResponse processChatInteraction(
            Conversation conversation, ChatMessageRequest request)
            throws InterruptedException, IOException {
        sendReceivedMetrics();

        Message userMsg = saveUserMessage(conversation, request.getContent());

        // --- API Call and Performance Metric ---
        long startTime = System.currentTimeMillis();
        Message responseMessage = ApiWrapper.query(userMsg);
        long duration = System.currentTimeMillis() - startTime;
        collectdClient.sendMetric("api_response_time", CollectdClient.CollectdType.GAUGE, duration);

        // --- API Call Counter Metric ---
        long newTotalCalls = totalApiCalls.incrementAndGet();
        collectdClient.sendMetric("api_calls_total", CollectdClient.CollectdType.COUNTER, newTotalCalls);
        // --- End of Metric ---

        logger.info("Received response from APIWrapper: '{}'", responseMessage.getContent());

        if (responseMessage.isEmpty()) {
            logger.info("Response message is empty");

            return new ChatMessageResponse("No response from the AI model.", conversation.getId());
        }

        Message botMessage =
                new Message(Message.MessageType.BOT, conversation, responseMessage.getContent());
        messageRepository.save(botMessage);
        logger.info("Bot message saved for conversation with id {}", conversation.getId());

        return new ChatMessageResponse(botMessage.getContent(), conversation.getId());
    }

    /**
     * Creates and persists a new Conversation entity.
     *
     * @return The newly created {@link Conversation}.
     */
    private Conversation createConversation() {
        Conversation conversation = new Conversation();
        conversationRepository.save(conversation);
        logger.info("New conversation started with ID: {}", conversation.getId());

        // --- Send Metric ---
        long totalConversations = conversationRepository.count();
        collectdClient.sendMetric(
                "conversations_created", CollectdClient.CollectdType.COUNTER, totalConversations);

        return conversation;
    }

    /**
     * Checks whether a conversation already exists under the ID and if so, it is loaded. If not, a
     * new one is created.
     *
     * @param conversationId The ID of a conversation.
     * @return The newly created or a loaded {@link Conversation}.
     */
    private Conversation getOrCreateConversation(Long conversationId) {
        if (conversationId != null) {
            return conversationRepository.findById(conversationId).orElseGet(this::createConversation);
        } else {
            Conversation conversation = createConversation();
            logger.info("Creating new conversation with id: {}", conversation.getId());

            return conversation;
        }
    }

    /**
     * Sends metrics about received messages to Collectd.
     */
    private void sendReceivedMetrics() {
        long count = messageRepository.count();
        collectdClient.sendMetric("received", CollectdClient.CollectdType.COUNTER, count);
        logger.info("Sending received metrics to Collectd: message count = {}", count);
    }

    /**
     * Creates and saves a user message to the database.
     *
     * @param conversation The conversation the message belongs to.
     * @param message      The content of the user's message.
     * @return The saved {@link Message} entity.
     */
    private Message saveUserMessage(Conversation conversation, String message) {
        Message userMsg = new Message(Message.MessageType.USER, conversation, message);
        messageRepository.save(userMsg);
        logger.info("User message saved...");

        return userMsg;
    }
}