import os
import json
import bcrypt
import pandas as pd
from typing import List
from pathlib import Path
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_huggingface import HuggingFaceEndpoint
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.schema import StrOutputParser

from operator import itemgetter
from pinecone import Pinecone
from langchain_pinecone import PineconeVectorStore
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain.memory import ConversationBufferMemory
from langchain.schema.runnable import Runnable, RunnablePassthrough, RunnableConfig, RunnableLambda
from langchain.callbacks.base import BaseCallbackHandler
from langchain.chains import (
    StuffDocumentsChain, ConversationalRetrievalChain, create_extraction_chain
)

import chainlit as cl
from chainlit.input_widget import TextInput, Select, Switch, Slider

from deep_translator import GoogleTranslator

from datetime import timedelta

@cl.step(type="tool")
async def LLMistral():
    os.environ['HUGGINGFACEHUB_API_TOKEN'] = os.environ['HUGGINGFACEHUB_API_TOKEN']
    #repo_id = "mistralai/Ministral-8B-Instruct-2410"
    #repo_id = "mistralai/Mixtral-8x7B-Instruct-v0.1"
    #repo_id = "nvidia/Mistral-NeMo-Minitron-8B-Base"
    repo_id = "mistralai/Mistral-7B-Instruct-v0.2"
    #repo_id = "mistralai/Mistral-7B-Instruct-v0.3"
    #repo_id = "allenai/OLMoE-1B-7B-0924-Instruct"
    llm = HuggingFaceEndpoint(
        repo_id=repo_id, max_new_tokens=5300, temperature=0.5, task="text2text-generation", streaming=True
    )
    return llm
    
@cl.step(type="tool")
async def VectorDatabase(categorie):
    if categorie == "year" or categorie == "videosTC":
        index_name = "all-jdlp"
        embeddings = HuggingFaceEmbeddings()
        vectorstore = PineconeVectorStore(
            index_name=index_name, embedding=embeddings, pinecone_api_key=os.getenv('PINECONE_API_KEYJDLP')
        )
    return vectorstore

@cl.step(type="retrieval")
async def Retriever(categorie):
    vectorstore = await VectorDatabase(categorie)
    if categorie == "videosTC":
        retriever = vectorstore.as_retriever(search_type="similarity_score_threshold", search_kwargs={"score_threshold": .7, "k": 250,"filter": {"title": {"$eq": "videos-table-rondeia"}, "time": {"$gte": 1320}}})
    return retriever

@cl.step(type="embedding")
async def Search(input, categorie):
    vectorstore = await VectorDatabase(categorie)
    results = []
    test = []
    sources_text = ""
    sources_offres = ""
    verbatim_text = ""
    count = 0
    countOffres = 0
    if categorie == "videosTC":
        search = vectorstore.similarity_search(input,k=50, filter={"title": {"$eq": "videos-table-rondeia"}, "time": {"$gte": 1320}})
        for i in range(0,len(search)):
            if count <= 23:
                count = count + 1
                timeSeq = search[i].metadata["time"]
                timeSeqRound = round(timeSeq)
                time = timedelta(seconds=timeSeqRound)
                sources_text = sources_text + '<div class="gridvid"><a target="_blank" title="' + search[i].metadata['titre'] + ' : ...' + search[i].page_content + '" href="' + search[i].metadata['video'] + '#start=' + str(timeSeq) + '"><img src="' + search[i].metadata['image'] + '" width="100%" alt="' + search[i].metadata['titre'] + ' : ...' + search[i].page_content + '"/><p>🕓 ' + str(time) + ' : ...' + search[i].page_content + ' : ' + search[i].metadata['titre'] + '</p></a></div>'
                sources_audio = sources_text + '<div class="gridvid"><a target="_blank" title="' + search[i].metadata['titre'] + ' : ...' + search[i].page_content + '" href="' + search[i].metadata['Audio'] + '#start=' + str(timeSeq) + '"><img src="' + search[i].metadata['thumb'] + '" width="100%" alt="' + search[i].metadata['titre'] + ' : ...' + search[i].page_content + '"/><p>🕓 ' + str(time) + ' : ...' + search[i].page_content + ' : ' + search[i].metadata['titre'] + '</p></a></div>'
                verbatim_text = verbatim_text + "<p style='font-size:0.8rem'>" + str(count) + ". " + search[i].metadata['titre'] + "</p><p style='font-size:0.8rem'>🕓 "+ str(time) + " : " + search[i].page_content + "</p>"
            
    results = [sources_text, verbatim_text, sources_audio]
    return results
        
@cl.set_chat_profiles
async def chat_profile():
    return [
        cl.ChatProfile(name="Table ronde autour de l'IA : «IA et gestes professionnels de l’enseignant»",markdown_description="VidĂ©o exploratoire autour de l'Ă©vĂ©nement",icon="/public/logo-ofipe.png",),
    ]
    
#@cl.on_chat_start
#async def on_chat_start():
#    await cl.Message(f"Bonjour").send()
    
@cl.set_starters
async def set_starters():
    return [
        cl.Starter(
            label="L'IA comme outil pour les enseignants et les Ă©tudiants",
            message="Comment les enseignants et les étudiants peuvent-ils utiliser l'IA comme un outil pour améliorer l'apprentissage et l'enseignement ?",
            icon="/public/videocam-theme1.svg",
            ),

        cl.Starter(
            label="Les limites de l'IA et la nécessité de l'intervention humaine",
            message="Quelles sont les limites de l'IA et pourquoi est-il important que les enseignants interviennent dans le processus d'apprentissage avec l'IA ?",
            icon="/public/videocam-theme2.svg",
            ),
        cl.Starter(
            label="L'importance de l'esprit critique face Ă  l'IA",
            message="Comment encourager les étudiants à développer un esprit critique face à l'IA et à ses résultats ?",
            icon="/public/videocam-theme3.svg",
            ),
        cl.Starter(
            label="L'éthique et l'IA dans les enseignements du supérieur",
            message="Quels sont les enjeux éthiques liés à l'utilisation de l'IA dans les enseignements du supérieur et comment y faire face ?",
            icon="/public/videocam-theme4.svg",
            ),
        cl.Starter(
            label="Les impacts de l'IA sur les métiers et les formations",
            message="Comment l'IA va transformer les métiers et les formations du supérieur et quels sont les défis à relever ?",
            icon="/public/videocam-theme5.svg",
            ),
        cl.Starter(
            label="Les limites de l'IA et les défis technologiques",
            message="Quelles sont les limites actuelles de l'IA et quels sont les défis technologiques à relever pour améliorer son utilisation dans les enseignements du supérieur ?",
            icon="/public/videocam-theme6.svg",
            )
        ]
    
@cl.on_message
async def on_message(message: cl.Message):
    if cl.context.session.client_type == "copilot":
        await cl.Message(f"Type : {message.type}").send()
        if message.type == "system_message":
          # do something with the message
            await cl.Message(f"Lien : ").send()
            return
            
    model = await LLMistral()
    retriever = await Retriever("videosTC")
    ########## Chain with streaming ##########
    message_history = ChatMessageHistory()
    memory = ConversationBufferMemory(memory_key="chat_history",output_key="answer",chat_memory=message_history,return_messages=True)
    
    qa = ConversationalRetrievalChain.from_llm(
        model,
        memory=memory,
        chain_type="stuff",
        return_source_documents=True,
        verbose=False,
        retriever=retriever
    )
    
    msg = cl.Message(content="")
    
    class PostMessageHandler(BaseCallbackHandler):
        """
        Callback handler for handling the retriever and LLM processes.
        Used to post the sources of the retrieved documents as a Chainlit element.
        """

        def __init__(self, msg: cl.Message):
            BaseCallbackHandler.__init__(self)
            self.msg = msg
            self.sources = set()  # To store unique pairs

        def on_retriever_end(self, documents, *, run_id, parent_run_id, **kwargs):
            for d in documents:
                source_page_pair = (d.metadata['source'], d.metadata['page'])
                self.sources.add(source_page_pair)  # Add unique pairs to the set

        def on_llm_end(self, response, *, run_id, parent_run_id, **kwargs):
                sources_text = "\n".join([f"{source}#page={page}" for source, page in self.sources])
                self.msg.elements.append(
                    cl.Text(name="Sources", content=sources_text, display="inline")
                )
        
    cb = cl.AsyncLangchainCallbackHandler()
    results = await qa.acall("Contexte : Vous ĂȘtes un chercheur de l'enseignement supĂ©rieur et vous ĂȘtes douĂ© pour faire des analyses d'articles de recherche sur les thĂ©matiques liĂ©es Ă  la pĂ©dagogie, en fonction des critĂšres dĂ©finis ci-avant. En fonction des informations suivantes et du contexte suivant seulement et strictement, rĂ©pondez en langue française strictement Ă  la question ci-dessous, en 5000 mots au moins. En plus, tu crĂ©eras et tu afficheras, Ă  la fin de ta rĂ©ponse, 3 questions supplĂ©mentaires en relation avec le contexte initial, Ă  chaque Ă©tape de la conversation. Tu Ă©criras et tu afficheras les 3 questions supplĂ©mentaires en relation avec le contexte initial, Ă  la fin de ta rĂ©ponse, avec un titrage de niveau 1 qui a pour titre \"Questions en relation avec le contexte : \". Lorsque cela est possible, cite les sources du contexte. Si vous ne pouvez pas rĂ©pondre Ă  la question sur la base des informations, dites que vous ne trouvez pas de rĂ©ponse ou que vous ne parvenez pas Ă  trouver de rĂ©ponse. Essayez donc de comprendre en profondeur le contexte et rĂ©pondez uniquement en vous basant sur les informations fournies. Ne gĂ©nĂ©rez pas de rĂ©ponses non pertinentes. Question : " + message.content, callbacks=[cb])
    #results = await qa.ainvoke({"question":"Contexte : Vous ĂȘtes un chercheur de l'enseignement supĂ©rieur et vous ĂȘtes douĂ© pour faire des analyses d'articles de recherche sur les thĂ©matiques liĂ©es Ă  la pĂ©dagogie, en fonction des critĂšres dĂ©finis ci-avant. En fonction des informations suivantes et du contexte suivant seulement et strictement, rĂ©pondez en langue française strictement Ă  la question ci-dessous, en exactement 5000 mots. En plus, tu crĂ©eras et tu afficheras, Ă  la fin de ta rĂ©ponse, 3 questions supplĂ©mentaires en relation avec le contexte initial, Ă  chaque Ă©tape de la conversation. Tu Ă©criras et tu afficheras les 3 questions supplĂ©mentaires en relation avec le contexte initial, Ă  la fin de ta rĂ©ponse, avec un titrage de niveau 1 qui a pour titre \"Questions en relation avec le contexte : \". Lorsque cela est possible, cite les sources du contexte. Si vous ne pouvez pas rĂ©pondre Ă  la question sur la base des informations, dites que vous ne trouvez pas de rĂ©ponse ou que vous ne parvenez pas Ă  trouver de rĂ©ponse. Essayez donc de comprendre en profondeur le contexte et rĂ©pondez uniquement en vous basant sur les informations fournies. Ne gĂ©nĂ©rez pas de rĂ©ponses non pertinentes. Question : " + message.content})

    #async for chunk in qa.astream({"question":"Contexte : Vous ĂȘtes un chercheur de l'enseignement supĂ©rieur et vous ĂȘtes douĂ© pour faire des analyses d'articles de recherche sur les thĂ©matiques liĂ©es Ă  la pĂ©dagogie, en fonction des critĂšres dĂ©finis ci-avant. En fonction des informations suivantes et du contexte suivant seulement et strictement, rĂ©pondez en langue française strictement et exclusivement Ă  la question ci-dessous, en exactement 5000 mots. En plus, tu crĂ©eras et tu afficheras, Ă  la fin de ta rĂ©ponse, 3 questions supplĂ©mentaires en relation avec le contexte initial, Ă  chaque Ă©tape de la conversation. Tu Ă©criras et tu afficheras les 3 questions supplĂ©mentaires en relation avec le contexte initial, Ă  la fin de ta rĂ©ponse en langue française seulement, avec un titrage de niveau 1 qui a pour titre \"Questions en relation avec le contexte : \". Lorsque cela est possible, cite les sources du contexte. Si vous ne pouvez pas rĂ©pondre Ă  la question sur la base des informations, dites que vous ne trouvez pas de rĂ©ponse ou que vous ne parvenez pas Ă  trouver de rĂ©ponse. Essayez donc de comprendre en profondeur le contexte et rĂ©pondez uniquement en vous basant sur les informations fournies. Ne gĂ©nĂ©rez pas de rĂ©ponses non pertinentes. Question : " + message.content},
    #                config=RunnableConfig(callbacks=[cl.AsyncLangchainCallbackHandler(stream_final_answer=True)])):
    #        await msg.stream_token(chunk)
        
    answer = results["answer"]

    await cl.Message(content=GoogleTranslator(source='auto', target='fr').translate(answer[0:5000])).send()
    
    search = await Search(message.content, "videosTC")
    
    sources = [
        cl.Text(name="Sources vidéo", content=search[0], display="inline")
    ]
    await cl.Message(
        content="Vidéos : ",
        elements=sources,
    ).send()
    #if search[2]:
    #    sourcesOffres = [
    #        cl.Text(name="Sources audio", content=search[2], display="inline")
    #    ]
    #    await cl.Message(
    #        content="Audio : ",
    #        elements=sourcesOffres,
    #    ).send()
    verbatim = [
        cl.Text(name="Verbatim", content=search[1], display="side")
    ]
    await cl.Message(
        content="📚 Liste des Verbatim ",
        elements=verbatim,
    ).send()