package xx.xxxxx.xxxxx; // à modifier...


import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.faces.model.SelectItem;
import jakarta.faces.view.ViewScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.json.*;
import jakarta.json.stream.JsonGenerator;
import jakarta.ws.rs.client.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Named
@ViewScoped
public class Bb2 implements Serializable {
    private String question;
    private String reponse;
    /**
     * La conversation depuis le début.
     */
    private StringBuilder conversation = new StringBuilder();
    private JsonObject requeteJson;
    /**
     * Rôle que l'on veut attribuer à "ChatGPT"
     */
    private String systemRole = "helpful assistant";
    /**
     * Si true, les 2 textarea qui affichent les documents JSON de la requête et de la réponse seront affichés.
     */
    private boolean debug = false;
    private String texteRequeteJson;
    private String texteReponseJson;
    private JsonArray messagesJson;
    /**
     * Pour ajouter une nouvelle valeur à la fin du tableau JSON "messages" dans le document JSON de la requête.
     * Le "-" final indique que la valeur sera ajoutée à la fin du tableau.
     */
    private final JsonPointer pointer = Json.createPointer(("/messages/-"));

    @Inject
    private FacesContext facesContext;

    /**
     * Initialise requeteJson avec un JsonObject qui correspond à ce document Json :
     * {
     * "model":"gpt-3.5-turbo",
     * "messages":[
     * {"role":"system","content":"You are a helpful assistant."}
     * ]
     * }
     */
    public Bb2() {

    }

    private void createrequeteJson() {
        JsonObjectBuilder builder = Json.createObjectBuilder()
                .add("model", "gpt-3.5-turbo");
        JsonObject systemRoleJson = Json.createObjectBuilder()
                .add("role", "system")
                .add("content", "you are a " + systemRole)
                .build();
        this.messagesJson = Json.createArrayBuilder()
                .add(systemRoleJson).build();
        this.requeteJson = builder.add("messages", messagesJson).build();
    }
    public String getSystemRole() {
        return systemRole;
    }

    public void setSystemRole(String systemRole) {
        this.systemRole = systemRole;
    }

    public String getQuestion() {
        return question;
    }

    public void setQuestion(String question) {
        this.question = question;
    }

    public String getReponse() {
        return reponse;
    }

    public void setReponse(String reponse) {
        this.reponse = reponse;
    }

    public String getConversation() {
        return conversation.toString();
    }

    public void setConversation(String conversation) {
        this.conversation = new StringBuilder(conversation);
    }

    public boolean isDebug() {
        return debug;
    }

    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    public void toggleDebug() {
        this.setDebug(!isDebug());
    }

    public String getTexteRequeteJson() {
        return texteRequeteJson;
    }

    public void setTexteRequeteJson(String texteRequeteJson) {
        this.texteRequeteJson = texteRequeteJson;
    }

    public String getTexteReponseJson() {
        return texteReponseJson;
    }

    public void setTexteReponseJson(String texteReponseJson) {
        this.texteReponseJson = texteReponseJson;
    }

    /**
     * Envoie la question à ChatGPT.
     * Format du document JSON envoyé dans la requête vers l'API.
     * {
     * "model": "gpt-3.5-turbo",
     * "messages": [
     * {
     * "role": "system",
     * "content": "You are a helpful assistant."
     * },
     * {
     * "role": "user",
     * "content": "Question utilisateur"
     * },
     * {
     * "role":"assistant",
     * "content":"Réponse API"
     * },
     * {
     * "role": "user",
     * "content": "Autre question utilisateur"
     * }
     * ]
     * }
     *
     * @return null pour rester sur la même page.
     */
    public String envoyer() {
        if (question.isBlank()) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR,
                    "Texte question vide", "Il manque le texte de la question");
            facesContext.addMessage(null, message);
            return null;
        }
        if (this.requeteJson == null) {
            createrequeteJson();
        }
        // 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 chatgptKey = System.getenv("CHATGPT_KEY");
        // Client REST pour envoyer des requêtes vers les endpoints de l'API d'OpenAI
        Client clientRest = ClientBuilder.newClient();
        // Endpoint REST pour envoyer la question à l'API.
        WebTarget target = clientRest.target("https://api.openai.com/v1/chat/completions");
        Invocation.Builder request = target.request(MediaType.APPLICATION_JSON_TYPE);
        // Met la clé secrète dans le header "Authorization" de la requête.
        request.header("Authorization", "Bearer " + chatgptKey);
        // Ce qui sera envoyé dans le corps de la requête POST
        String requestBody = ajouteQuestionDansJsonRequete(question);
        Entity<String> entity = Entity.entity(requestBody, MediaType.APPLICATION_JSON_TYPE);
        Response response = request.post(entity);
        if (response.getStatus() == 200) {
            String jsonResponse = response.readEntity(String.class);
            // Pour afficher le document de la réponse si en mode debug
            this.texteReponseJson = jsonResponse;
            this.reponse = extractReponse(jsonResponse); // pour afficher la réponse
            conversation.append("User:\n").append(question).append("\nGPT :\n").append(reponse).append("\n");
        } else {
            this.reponse = "Erreur requête : " + response.getStatus();
        }
        clientRest.close();
        return null;
    }

    /**
     * Modifie le JSON de la requete pour ajouter le JsonObject lié à la nouvelle question dans messagesJson.
     * Il faut ajouter au tableau JSON, valeur de la clé "messages", un objet JSON du type
     * {
     * "role": "user",
     * "content": "Nouvelle question de l'utilisateur"
     * }
     *
     * @param nouvelleQuestion question posée par l'utilsateur.
     * @return le texte du document JSON de la requête.
     */
    private String ajouteQuestionDansJsonRequete(String nouvelleQuestion) {
        // Crée le nouveau JsonObject qui correspond à la nouvelle question
        JsonObject nouveauMessageJson = Json.createObjectBuilder()
                .add("role", "user")
                .add("content", nouvelleQuestion)
                .build();
        // Ajoute ce nouveau JsonObjet dans messagesJson
        this.requeteJson = this.pointer.add(this.requeteJson, nouveauMessageJson);
        this.texteRequeteJson = prettyPrinting(requeteJson);
        return this.requeteJson.toString();
    }


    /**
     * Retourne le texte formatté du document JSON pour un affichage plus agréable.
     *
     * @param jsonObject l'objet JSON dont on veut une forme formattée.
     * @return la forme formattée
     */
    private String prettyPrinting(JsonObject jsonObject) {
        Map<String, Boolean> config = new HashMap<>();
        config.put(JsonGenerator.PRETTY_PRINTING, true);
        JsonWriterFactory writerFactory = Json.createWriterFactory(config);
        StringWriter stringWriter = new StringWriter();
        try (JsonWriter jsonWriter = writerFactory.createWriter(stringWriter)) {
            jsonWriter.write(jsonObject);
        }
        return stringWriter.toString();
    }

    /**
     * Extrait la réponse de ChatGPT et ajoute la réponse à jsonRequete pour garder la conversation dans
     * la prochaine requête.
     * Le document JSON de la réponse a cette structure :
     * <pre>{@code}
     * {
     *   "id": "chatcmpl-872b0vL97t7uSw3xPVAZjSbxwe95Z",
     *   "object": "chat.completion",
     *   "created": 1696688966,
     *   "model": "gpt-3.5-turbo-0613",
     *   "choices": [
     *     {
     *       "index": 0,
     *       "message": {
     *         "role": "assistant",
     *         "content": "Bonjour, comment ça va ?"
     *       },
     *       "finish_reason": "stop"
     *     }
     *   ],
     *   "usage": {
     *     "prompt_tokens": 32,
     *     "completion_tokens": 6,
     *     "total_tokens": 38
     *   }
     * }
     * }</pre>
     *
     * @param json le document JSON de la réponse.
     * @return juste la valeur de content qui contient la réponse à la question.
     */
    private String extractReponse(String json) {
        try (JsonReader jsonReader = Json.createReader(new StringReader(json))) {
            JsonObject jsonObject = jsonReader.readObject();
            JsonObject messageReponse = jsonObject
                    .getJsonArray("choices")
                    .getJsonObject(0)
                    .getJsonObject("message");
            // Ajoute l'objet JSON de la réponse de l'API au JSON de la prochaine requête
            this.requeteJson = this.pointer.add(this.requeteJson, messageReponse);
            // Extrait seulement le texte de la réponse
            return messageReponse.getString("content");
        }
    }

    public List<SelectItem> getSystemRoles() {
        List<SelectItem> listeSystemRoles = new ArrayList<>();
        listeSystemRoles.add(new SelectItem("you are a helpful assistant", "Assistant"));
        String role = """
                You are an interpreter. You translate from English to French and from French to English.
                If the user type a French text, you translate it into English.
                If the user type an English text, you translate it into French.
                """;
        listeSystemRoles.add(new SelectItem(role, "Traducteur Anglais-Français"));
        role = """
                Your are a travel guide. If the user type the name of a country or of a town,
                you tell them what are the main places to visit in the country or the town
                are you tell them the average price of a meal.
                """;
        listeSystemRoles.add(new SelectItem(role, "Guide touristique"));
        return listeSystemRoles;
    }