import os
import random
import json
import faiss
import numpy as np
import streamlit as st
from dotenv import load_dotenv
from PyPDF2 import PdfReader
from sklearn.feature_extraction.text import TfidfVectorizer
import anthropic
from langchain import LLMChain
from langchain.output_parsers import StructuredOutputParser, ResponseSchema
from langchain.prompts import PromptTemplate
from langchain.llms import Anthropic

# Load environment variables
load_dotenv()
ANTHROPIC_API_KEY = os.getenv('ANTHROPIC_API_KEY')

if not ANTHROPIC_API_KEY:
    st.error("Anthropic API key not found. Please set ANTHROPIC_API_KEY in the .env file.")
    st.stop()

# Initialize LangChain Anthropic LLM
llm = Anthropic(api_key=ANTHROPIC_API_KEY, model="claude-v1")  # You can choose other models like 'claude-v1.3' or 'claude-instant-v1'

def extract_text_and_create_chunks(pdf_file, chunk_size=512):
    """Extracts text from a PDF and splits it into manageable chunks."""
    text = ""
    try:
        reader = PdfReader(pdf_file)
        for page in reader.pages:
            page_text = page.extract_text()
            if page_text:
                text += page_text
    except Exception as e:
        st.error(f"Failed to read PDF file: {e}")
    text_chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
    return text_chunks

def index_text_chunks(text_chunks):
    """Indexes text chunks using FAISS for efficient similarity searches."""
    vectorizer = TfidfVectorizer()
    vectors = vectorizer.fit_transform(text_chunks).toarray().astype('float32')
    index = faiss.IndexFlatL2(vectors.shape[1])
    index.add(vectors)
    return index, vectorizer

def generate_question_and_answer(content):
    """Generates a multiple-choice question and the correct answer based on the provided content."""
    response_schemas = [
        ResponseSchema(name="question", description="The multiple-choice question"),
        ResponseSchema(name="option_a", description="Option A for the question"),
        ResponseSchema(name="option_b", description="Option B for the question"),
        ResponseSchema(name="option_c", description="Option C for the question"),
        ResponseSchema(name="option_d", description="Option D for the question"),
        ResponseSchema(name="correct_answer", description="The correct answer for the question which should be one of 'A', 'B', 'C', 'D'"),
    ]

    output_parser = StructuredOutputParser.from_response_schemas(response_schemas)

    prompt = PromptTemplate(
        input_variables=["content"],
        template="""
Generate a multiple-choice question based on the following content.
Content: {content}
{format_instructions}
""",
        partial_variables={"format_instructions": output_parser.get_format_instructions()},
    ) 

    chain = LLMChain(prompt=prompt, llm=llm)
    
    try:
        result = chain.run(content)
        result = output_parser.parse(result)
    except Exception as e:
        st.error(f"Error generating question: {e}")
        return None, None, None, None

    question = result.get("question")
    correct_answer = result.get("correct_answer")
    options = [
        result.get("option_a"),
        result.get("option_b"),
        result.get("option_c"),
        result.get("option_d"),
    ]

    # Shuffle options and adjust correct answer accordingly
    options_shuffled = options.copy()
    random.shuffle(options_shuffled)
    correct_option_text = result.get(f"option_{correct_answer.lower()}")

    try:
        correct_answer_index = options_shuffled.index(correct_option_text) + 1  # 1-based index
    except ValueError:
        correct_answer_index = None  # Handle cases where the correct answer is not found

    explanation = generate_explanation(question, correct_option_text)
    question_display = f"{question}\n\n1. {options_shuffled[0]}\n2. {options_shuffled[1]}\n3. {options_shuffled[2]}\n4. {options_shuffled[3]}"
    return question_display, options_shuffled, correct_answer_index, explanation

def generate_explanation(question, correct_answer_text):
    """Generates an explanation for the provided question and correct answer."""
    prompt = PromptTemplate(
        input_variables=["question", "correct_answer"],
        template="Provide an explanation for the following question and answer:\n\nQuestion: {question}\nCorrect Answer: {correct_answer}\n\nExplanation:"
    )
    chain = LLMChain(prompt=prompt, llm=llm)
    try:
        explanation = chain.run({"question": question, "correct_answer": correct_answer_text})
    except Exception as e:
        explanation = "No explanation available."
        st.warning(f"Failed to generate explanation: {e}")
    return explanation

def start_quiz(pdf_file, num_questions):
    """Starts the quiz by processing the PDF and generating the first question."""
    if pdf_file is None:
        st.warning("Please upload a PDF file to start the quiz.")
        return None

    text_chunks = extract_text_and_create_chunks(pdf_file)
    if not text_chunks:
        st.error("No text found in the PDF file.")
        return None

    index, vectorizer = index_text_chunks(text_chunks)
    used_indices = []
    question_data = {}

    # Select initial question
    if text_chunks:
        initial_index = random.choice(range(len(text_chunks)))
        used_indices.append(initial_index)
        question, options, correct_answer, explanation = generate_question_and_answer(text_chunks[initial_index])
        if question:
            question_data = {
                "question": question,
                "options": options,
                "correct_answer": correct_answer,
                "explanation": explanation
            }

    # Initialize session state
    st.session_state['text_chunks'] = text_chunks
    st.session_state['index'] = index
    st.session_state['vectorizer'] = vectorizer
    st.session_state['used_indices'] = used_indices
    st.session_state['score'] = 0
    st.session_state['count'] = 1
    st.session_state['total_questions'] = num_questions
    st.session_state['current_question'] = question_data

    return question_data

def submit_answer(selected_option):
    """Processes the user's answer and updates the quiz state."""
    if 'current_question' not in st.session_state or not st.session_state['current_question']:
        st.warning("No active quiz. Please start the quiz.")
        return

    current = st.session_state['current_question']
    correct = current['correct_answer']
    explanation = current['explanation']
    score = st.session_state['score']
    count = st.session_state['count']
    total = st.session_state['total_questions']

    if selected_option is None:
        st.warning("Please select an option before submitting.")
        return

    if selected_option == correct:
        st.success("Correct!")
        st.session_state['score'] += 1
    else:
        correct_option_text = current['options'][correct - 1] if correct else "Unknown"
        st.error(f"Incorrect! The correct answer was option {correct}: {correct_option_text}.")

    st.write(f"**Explanation:** {explanation}")

    st.session_state['count'] += 1

    if st.session_state['count'] > total:
        st.write(f"### 🎉 Quiz Completed! Your final score is {st.session_state['score']} out of {total}.")
        return

    # Generate next question
    text_chunks = st.session_state['text_chunks']
    used_indices = st.session_state['used_indices']
    remaining_indices = list(set(range(len(text_chunks))) - set(used_indices))

    if not remaining_indices:
        st.write("No more questions available.")
        st.write(f"### 🎉 Quiz Completed! Your final score is {st.session_state['score']} out of {total}.")
        return

    next_index = random.choice(remaining_indices)
    st.session_state['used_indices'].append(next_index)
    question, options, correct_answer, explanation = generate_question_and_answer(text_chunks[next_index])

    if question:
        st.session_state['current_question'] = {
            "question": question,
            "options": options,
            "correct_answer": correct_answer,
            "explanation": explanation
        }
    else:
        st.session_state['current_question'] = None
        st.write("Failed to generate the next question.")

def main():
    st.set_page_config(page_title="📚 PDF Quiz Application", layout="wide")
    st.title("📚 PDF Quiz Application")

    if 'current_question' not in st.session_state:
        st.session_state['current_question'] = None

    with st.sidebar:
        st.header("🎯 Quiz Settings")
        pdf_file = st.file_uploader("📥 Upload a PDF file", type=["pdf"])
        num_questions = st.slider("🔢 Number of Questions", min_value=1, max_value=50, value=10, step=1)
        start_btn = st.button("🚀 Start Quiz")
        st.markdown("---")
        st.markdown(f"**Score:** {st.session_state.get('score', 0)} / {st.session_state.get('total_questions', 0)}")

    if start_btn:
        question_data = start_quiz(pdf_file, num_questions)
        if question_data:
            st.experimental_rerun()

    if st.session_state['current_question']:
        current = st.session_state['current_question']
        st.write(f"### Question {st.session_state['count']} of {st.session_state['total_questions']}")
        st.write(current['question'])
        selected_option = st.radio(
            "📋 Select an answer:",
            options=['1', '2', '3', '4'],
            format_func=lambda x: current['options'][int(x)-1]
        )

        if st.button("✅ Submit Answer"):
            if selected_option:
                submit_answer(int(selected_option))
                st.experimental_rerun()

    if 'count' in st.session_state and st.session_state['count'] > st.session_state.get('total_questions', 0) and st.session_state['count'] != 1:
        st.write(f"### 🎉 Quiz Completed! Your final score is {st.session_state['score']} out of {st.session_state['total_questions']}.")
        st.stop()

if __name__ == "__main__":
    main()    , getting this error : Error generating question: Completions.create() got an unexpected keyword argument 'api_key'