package xx.xxxxx.streamingaveclangchain.gpt; // A modifier

import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.TokenWindowChatMemory;
import dev.langchain4j.model.Tokenizer;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import dev.langchain4j.model.openai.OpenAiTokenizer;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.TokenStream;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Dependent;
import jakarta.faces.push.Push;
import jakarta.faces.push.PushContext;
import jakarta.inject.Inject;

import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;

/**
 * Gère l'interface avec l'API OpenAI en utilisant LangChain4j.
 * Son rôle est essentiellement de lancer une requête à chaque nouvelle
 * question qu'on veut envoyer à l'API.
 * <p>
 * De portée dépendent pour réinitialiser la conversation à chaque fois que
 * l'instance qui l'utilise est renouvelée.
 * Par exemple, si l'instance qui l'utilise est de portée View, la conversation est
 * réinitialisée à chaque fois que l'utilisateur quitte la page en cours.
 * Cette classe doit implémenter Serializable pour être injectée dans un bean JSF de portée View.
 */
@Dependent
public class OpenAiClient implements java.io.Serializable {
    private static final Logger logger = Logger.getLogger("xx.xxxxx.streamingaveclangchain.gpt.OpenAiClient"); // A modifier
    /**
     * Modèle de l'API.
     */
    private String modelName = "gpt-4o-mini";
    private final StreamingChatLanguageModel model;

    private String systemRole;
    private ChatMemory chatMemory;
    private String reponse;

    private final Assistant assistant;

    /**
     * PushContext pour envoyer des messages aux clients via le websocket.
     */
    @Inject
    @Push(channel = "chat")
    private PushContext webSocketPourChat;

    public OpenAiClient() {
        // Récupère la clé secrète pour travailler avec l'API d'OpenAI, mise dans une variable d'environnement
        // du système d'exploitation.
        String openAiKey = System.getenv("CHATGPT_KEY");
        logger.info("Clé secrète OpenAI: " + openAiKey);
        // Utilisé pour limiter la taille de la mémoire de chat à 1000 tokens
        Tokenizer tokenizer = new OpenAiTokenizer(modelName);
        this.chatMemory = TokenWindowChatMemory.withMaxTokens(1000, tokenizer);
        this.model = OpenAiStreamingChatModel.withApiKey(openAiKey);
        this.assistant = AiServices.builder(Assistant.class)
                .streamingChatLanguageModel(this.model)
                .chatMemory(chatMemory)
                .build();
    }

    public void setSystemRole(String systemRole) {
        this.systemRole = systemRole; // Inutile ?
        this.chatMemory.add(new SystemMessage(systemRole));
    }

    public String getReponse() {
        return this.reponse;
    }

    /**
     * Envoie une requête à l'API OpenAI.
     * Déclenche le streaming pour que les tokens de la réponse soient récupérés et affichés.
     * Quand le streaming est terminé, la réponse est ajoutée à la mémoire de chat et mise à 
     * disposition pour être affichée et le message "streamingfinished" est envoyé dans le websocket.
     *
     * @param question la question.
     * @throws ExecutionException, InterruptedException à cause de CompletableFuture.
     */
    public void envoyerRequete(String question) throws ExecutionException, InterruptedException {
        // Réinitialise la réponse pour que la nouvelle réponse soit affichée seulement 
        // après que le streaming soit terminé.
        this.reponse = "";
        TokenStream tokenStream = this.assistant.chat(question);
        tokenStream.onNext(token -> {
                    logger.info("**" + token);
                    // Envoie le token dans le websocket
                    webSocketPourChat.send(token);
                })
                .onComplete(response -> {
                    this.reponse = response.content().text();
                    // Ajoute la réponse à la mémoire de chat
                    this.chatMemory.add(response.content());
                    // Envoie un message pour dire que le streaming est terminé.
                    // Sera traité par le client pour afficher la réponse.
                    webSocketPourChat.send("streamingfinished");
                })
                .onError(throwable -> {
                    // TODO: Gérer l'erreur
                    throwable.printStackTrace();
                })
                .start();

    }

}