#conversation history
from flask import Flask, render_template, request, jsonify
import time
import numpy as np
import faiss
from dotenv import load_dotenv
from mistralai import Mistral
from langchain_community.document_loaders import TextLoader
import threading
from itertools import cycle
from cachetools import TTLCache
from queue import Queue
import os
import mysql.connector
import atexit
from flask_cors import CORS #

# Load environment variables
load_dotenv()

# MySQL configuration
db_config = {
    'host': 'localhost',
    'user': 'rabbyfar_chatbot',
    'password': 'JIwEF(fpWDBF',
    'database': 'rabbyfar_chatbot',
}

conn = mysql.connector.connect(**db_config)
cursor = conn.cursor()

# API tokens and limits setup
apis = [
    {"api_key": "t0zJXzm2HklV9YgNBkjHyCC9z775RYAM", "minute_limit": 500000, "monthly_limit": 100000000, "used_in_last_minute": 0, "used_in_month": 0},
    {"api_key": "AwZDHsbZsOltQX4L3sCBlIKNk5JtsRFE", "minute_limit": 500000, "monthly_limit": 100000000, "used_in_last_minute": 0, "used_in_month": 0},
    {"api_key": "wtiP9b9HFvZdbyhTByQLjLeAXctbzp3F", "minute_limit": 500000, "monthly_limit": 100000000, "used_in_last_minute": 0, "used_in_month": 0},
]

api_cycle = cycle(apis)

# Function to get the next available API
def get_next_api():
    for api in api_cycle:
        if api["used_in_last_minute"] < api["minute_limit"] and api["used_in_month"] < api["monthly_limit"]:
            api["used_in_last_minute"] += 1
            api["used_in_month"] += 1
            return Mistral(api_key=api["api_key"])
    raise Exception("All APIs have exceeded their limits.")

# Reset usage statistics every minute
def reset_minute_usage():
    while True:
        time.sleep(60)
        for api in apis:
            api["used_in_last_minute"] = 0

# Reset usage statistics every month
def reset_monthly_usage():
    while True:
        time.sleep(30 * 24 * 60 * 60)
        for api in apis:
            api["used_in_month"] = 0

# Start the background tasks for resetting usage
threading.Thread(target=reset_minute_usage, daemon=True).start()
threading.Thread(target=reset_monthly_usage, daemon=True).start()

# Load data
loader = TextLoader(r"book.txt", encoding="utf-8")
docs = loader.load()
text = docs[0].page_content

# Chunk text data
chunk_size = 500
chunks = [text[i:i + chunk_size] for i in range(0, len(text), chunk_size)]

# Function to get text embedding with error handling
def get_text_embedding(input_text):
    try:
        client = get_next_api()
        embeddings_batch_response = client.embeddings.create(
            model="mistral-embed",
            inputs=[input_text]
        )
        return embeddings_batch_response.data[0].embedding
    except Exception as e:
        print(f"Error creating embeddings: {e}")
        raise

# Add a delay between API calls to avoid rate limiting
delay_seconds = 2

# Faiss index file path
index_file_path = r"embeddings_index_new.faiss"

# Faiss index setup
if os.path.exists(index_file_path):
    index = faiss.read_index(index_file_path)
    print("Loaded existing Faiss index.")
else:
    text_embeddings = []
    for chunk in chunks:
        embedding = get_text_embedding(chunk)
        text_embeddings.append(embedding)
        time.sleep(delay_seconds)

    text_embeddings = np.array(text_embeddings)
    d = text_embeddings.shape[1]
    index = faiss.IndexFlatL2(d)
    index.add(text_embeddings)
    faiss.write_index(index, index_file_path)
    print("New Faiss index created and saved.")

# Cache for storing responses
cache = TTLCache(maxsize=100, ttl=300)

# Request queue for managing simultaneous requests
request_queue = Queue()

# Memory to store previous questions and answers
conversation_memory = []

# Question counts to track repeated questions
question_counts = {}

# Generate response using Mistral
def run_mistral(prompt, model="open-mistral-nemo"):
    client = get_next_api()
    messages = [{"role": "user", "content": prompt}]
    time.sleep(delay_seconds)
    chat_response = client.chat.complete(model=model, messages=messages)
    return chat_response.choices[0].message.content

# Database integration functions
def check_in_database(question):
    query = "SELECT response FROM conversation_cache WHERE question = %s"
    cursor.execute(query, (question,))
    result = cursor.fetchone()
    return result[0] if result else None

def save_to_database(question, response):
    query = "INSERT INTO conversation_cache (question, response) VALUES (%s, %s)"
    cursor.execute(query, (question, response))
    conn.commit()


# Function to generate conversation history
def generate_conversation_history():
    history = ""
    for memory in conversation_memory:
        history += f"Question: {memory['question']}\nAnswer: {memory['answer']}\n"
    return history

# Process requests
def process_requests():
    while True:
        question, callback = request_queue.get()
        try:
            # Track repeated questions
            question_counts[question] = question_counts.get(question, 0) + 1

            if question_counts[question] >= 3:
                question_counts[question] = 0  # Reset counter
                answer = check_in_database(question) or "No response found in database."
            else:
                cached_response = check_in_database(question)
                if cached_response:
                    answer = cached_response
                else:
                    question_embedding = np.array([get_text_embedding(question)])
                    D, I = index.search(question_embedding, k=2)
                    retrieved_chunk = [chunks[i] for i in I[0]]
                    prompt = f"""
                     Context information is below.
                    ---------------------
                    {retrieved_chunk}
                    ---------------------
                    Prompt:You are a helpfull AI assiatant and your name is chatbot. Given the context information and not prior knowledge, always give answer in bangla.
                    Query: {question}
                    Answer:
                    """
                    answer = run_mistral(prompt)
                    save_to_database(question, answer)

            conversation_memory.append({"question": question, "answer": answer})
            callback(answer)
        except Exception as e:
            callback(str(e))
        finally:
            request_queue.task_done()

threading.Thread(target=process_requests, daemon=True).start()

# Flask setup
app = Flask(__name__)
CORS(app) #passing cors to all

@app.route('/')
def home():
    return render_template('index.html')

@app.route('/get', methods=['GET'])
def get_bot_response():
    question = request.args.get('msg')
    response_holder = []

    def callback(response):
        response_holder.append(response)

    request_queue.put((question, callback))
    request_queue.join()

    return jsonify(response_holder[0])

@atexit.register
def close_connection():
    if conn.is_connected():
        cursor.close()
        conn.close()

if __name__ == "__main__":
    app.run(debug=True)
