from tempfile import NamedTemporaryFile
import tempfile
import traceback
from typing import List
from fastapi import FastAPI, UploadFile, File, Form, Body
from fastapi.responses import JSONResponse
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter
from pydantic import BaseModel
from langchain_ollama import OllamaEmbeddings

import uvicorn
#from moviepy import VideoFileClip
from pydub import AudioSegment
from fastapi.datastructures import UploadFile
from fastapi import HTTPException
from io import BytesIO
import subprocess
import os
import asyncio
import requests
import time
from urllib.parse import urlparse

app = FastAPI()
from training import load_pdf, split_text_with_semantic_chunker, count_tokens_in_documents, save_documents_to_txt, create_and_save_embeddings, merge_all_faiss
from training_doc import load_doc, count_tokens_in_doc_documents
from training_ppt import load_ppt, count_tokens_in_ppt_documents


@app.get('/helloEdurigoAiChatAndGenQuestionsTesting')
def hello():
    return {'message': 'Hello from edurigo_ai testing env'}

""" TRAINING ( CREATING EMBBEDDINGS ) FOR LLAMA-3 MODEL """

# from training import load_and_split_document, create_embeddings, count_tokens

class TrainResponse(BaseModel):
    message: str
    token_count: int

class ErrorResponse(BaseModel):
    error: str    


def fetch_document_with_curl(url):
    try:
        # Use subprocess to call curl and fetch the document content with follow-redirects and fail-on-error
        result = subprocess.run(["curl", "-k", "-s", "-L", "-f", url], capture_output=True, check=True)
        return result.stdout
    except subprocess.CalledProcessError as e:
        stderr_msg = e.stderr.decode('utf-8', errors='ignore').strip() if e.stderr else str(e)
        raise HTTPException(status_code=400, detail=f"Error fetching document: {stderr_msg}")

@app.post("/train_with_document_edurigo_ai", response_model=TrainResponse, responses={400: {"model": ErrorResponse}, 500: {"model": ErrorResponse}})
def process_document(client_id: str = Form(...), document_url: str = Form(...), reference_id: str = Form(...)):
    # Check if document is in supported format

    path = urlparse(document_url).path
    file_extension = os.path.splitext(path)[1].lower()

    if file_extension not in ['.pdf', '.txt', '.docx', '.doc', '.pptx', '.ppt']:
        raise HTTPException(status_code=400, detail="Unsupported file type. Only .pdf, .txt, .docx, .doc, .pptx and .ppt are supported.")

    start_total = time.time()

    temp_file_path = None

    try:
        # STEP 1: Download
        t1 = time.time()
        # Fetch document content from the provided URL
        document_content = fetch_document_with_curl(document_url)
        print(f"[TIME] Download: {time.time() - t1:.2f}s")

        # Save document content to a temporary file
        #with tempfile.NamedTemporaryFile(delete=False, suffix=file_extension) as temp_file:
           # temp_file.write(document_content)
           # temp_file_path = temp_file.name
           # print(f"Temporary file saved at: {temp_file_path}")

        # STEP 2: Save temp file
        t2 = time.time()
        with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(document_url)[1]) as temp_file:
            temp_file.write(document_content)
            temp_file_path = temp_file.name
        print(f"[TIME] Save temp file: {time.time() - t2:.2f}s")

        # Load document pages from the PDF or other format
        # STEP 3: Load PDF
        # STEP 3: Load document
        t3 = time.time()

        if file_extension == '.pdf':
            # Perform magic bytes validation
            is_pdf = False
            try:
                with open(temp_file_path, 'rb') as f:
                    header = f.read(1024)
                    if b'%PDF' in header:
                        is_pdf = True
            except Exception as e:
                print(f"Error reading file header: {e}")

            if is_pdf:
                docs = load_pdf(temp_file_path)
            else:
                # The file has a .pdf extension but is NOT a valid PDF!
                # Let's inspect the content to see if it looks like HTML or text
                content_preview = ""
                try:
                    with open(temp_file_path, 'r', encoding='utf-8', errors='ignore') as f:
                        content_preview = f.read(2048).strip()
                except Exception as e:
                    pass

                # Check if it looks like HTML
                if content_preview.lower().startswith(('<html', '<!doctype', '<div', '<head', '<body')):
                    raise HTTPException(
                        status_code=400,
                        detail="The downloaded file is an HTML page (possibly an error page, login page, or redirect) instead of a valid PDF document. Please verify the URL."
                    )
                elif content_preview:
                    # It's a text file but has a .pdf extension! Fallback to TextLoader
                    print(f"Warning: File extension is .pdf but content is text starting with: {content_preview[:50]!r}. Falling back to TextLoader.")
                    from langchain_community.document_loaders import TextLoader
                    loader = TextLoader(temp_file_path, encoding='utf-8')
                    docs = loader.load()
                else:
                    raise HTTPException(
                        status_code=400,
                        detail="The file is empty or could not be read as a valid PDF document."
                    )

        elif file_extension == '.txt':
            from langchain_community.document_loaders import TextLoader

            loader = TextLoader(temp_file_path, encoding='utf-8')
            docs = loader.load()

        else:
            raise HTTPException(status_code=400, detail="Unsupported file type")

        print(f"[TIME] Load document: {time.time() - t3:.2f}s | Docs: {len(docs)}")

        # Initialize embeddings and split documents with semantic chunker
        t4 = time.time()
        embeddings = OllamaEmbeddings(model='nomic-embed-text')
        split_documents = split_text_with_semantic_chunker(docs, embeddings)
        #print("Text successfully split into semantic chunks.")
        #print(f"Documents after splitting: {len(split_documents)} chunks created.")
        print(f"[TIME] Split text: {time.time() - t4:.2f}s | Chunks: {len(split_documents)}")

        # Save split documents to a text file in the specified output directory
        t5 = time.time()
        output_dir = f'temp/{client_id}_{reference_id}'
        save_documents_to_txt(split_documents, output_dir)
        print(f"[TIME] Save text: {time.time() - t5:.2f}s")

        # Create and save embeddings for each split document
        t6 = time.time()
        create_and_save_embeddings(split_documents, client_id, reference_id)
        print(f"[TIME] Embeddings: {time.time() - t6:.2f}s")

        # STEP 7: Merge FAISS
        t7 = time.time()
        merged_embeddings = merge_all_faiss(client_id, reference_id)
        print(f"[TIME] Merge FAISS: {time.time() - t7:.2f}s")

        # STEP 8: Token count
        t8 = time.time()
        token_count = count_tokens_in_documents(docs)
        print(f"[TIME] Token count: {time.time() - t8:.2f}s")

        print(f"[TOTAL TIME]: {time.time() - start_total:.2f}s")

        return {"message": "Training and embedding creation completed successfully.","token_count": token_count}

    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Processing failed: {e}")

    finally:
         if temp_file_path and os.path.exists(temp_file_path):
                os.remove(temp_file_path)


""" TRAINING ( CREATING EMBEDDINGS ) FOR DOC/DOCX DOCUMENTS """

@app.post("/train_with_doc_edurigo_ai", response_model=TrainResponse, responses={400: {"model": ErrorResponse}, 500: {"model": ErrorResponse}})
def process_document_doc(client_id: str = Form(...), document_url: str = Form(...), reference_id: str = Form(...)):
    """Train embeddings from DOC/DOCX document files."""
    file_extension = os.path.splitext(document_url)[1].lower()
    if file_extension not in ['.doc', '.docx']:
        raise HTTPException(status_code=400, detail="Unsupported file type. Only .doc and .docx are supported.")

    start_total = time.time()

    temp_file_path = None

    try:
        # STEP 1: Download document
        t1 = time.time()
        document_content = fetch_document_with_curl(document_url)
        print(f"[DOC] [TIME] Download: {time.time() - t1:.2f}s")

        # STEP 2: Save temp file
        t2 = time.time()
        with tempfile.NamedTemporaryFile(delete=False, suffix=file_extension) as temp_file:
            temp_file.write(document_content)
            temp_file_path = temp_file.name
        print(f"[DOC] [TIME] Save temp file: {time.time() - t2:.2f}s")

        # STEP 3: Load DOC/DOCX
        t3 = time.time()
        docs = load_doc(temp_file_path)
        print(f"[DOC] [TIME] Load document: {time.time() - t3:.2f}s | Documents: {len(docs)}")

        if not docs:
            raise HTTPException(status_code=400, detail="No text content could be extracted from the document.")

        # STEP 4: Semantic chunking
        t4 = time.time()
        embeddings = OllamaEmbeddings(model='nomic-embed-text')
        split_documents = split_text_with_semantic_chunker(docs, embeddings)
        print(f"[DOC] [TIME] Split text: {time.time() - t4:.2f}s | Chunks: {len(split_documents)}")

        # STEP 5: Save text chunks
        t5 = time.time()
        output_dir = f'temp/{client_id}_{reference_id}'
        save_documents_to_txt(split_documents, output_dir)
        print(f"[DOC] [TIME] Save text: {time.time() - t5:.2f}s")

        # STEP 6: Create embeddings
        t6 = time.time()
        create_and_save_embeddings(split_documents, client_id, reference_id)
        print(f"[DOC] [TIME] Embeddings: {time.time() - t6:.2f}s")

        # STEP 7: Merge FAISS
        t7 = time.time()
        merge_all_faiss(client_id, reference_id)
        print(f"[DOC] [TIME] Merge FAISS: {time.time() - t7:.2f}s")

        # STEP 8: Token count
        t8 = time.time()
        token_count = count_tokens_in_doc_documents(docs)
        print(f"[DOC] [TIME] Token count: {time.time() - t8:.2f}s")

        print(f"[DOC] [TOTAL TIME]: {time.time() - start_total:.2f}s")

        return {"message": "DOC/DOCX training and embedding creation completed successfully.", "token_count": token_count}

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Processing failed: {e}")
    finally:
        if temp_file_path and os.path.exists(temp_file_path):
            os.remove(temp_file_path)


""" TRAINING ( CREATING EMBEDDINGS ) FOR PPT/PPTX PRESENTATIONS """

@app.post("/train_with_ppt_edurigo_ai", response_model=TrainResponse, responses={400: {"model": ErrorResponse}, 500: {"model": ErrorResponse}})
def process_document_ppt(client_id: str = Form(...), document_url: str = Form(...), reference_id: str = Form(...)):
    """Train embeddings from PPT/PPTX presentation files."""
    file_extension = os.path.splitext(document_url)[1].lower()
    if file_extension not in ['.ppt', '.pptx']:
        raise HTTPException(status_code=400, detail="Unsupported file type. Only .ppt and .pptx are supported.")

    start_total = time.time()
    temp_file_path = None

    try:
        # STEP 1: Download presentation
        t1 = time.time()
        document_content = fetch_document_with_curl(document_url)
        print(f"[PPT] [TIME] Download: {time.time() - t1:.2f}s")

        # STEP 2: Save temp file
        t2 = time.time()
        with tempfile.NamedTemporaryFile(delete=False, suffix=file_extension) as temp_file:
            temp_file.write(document_content)
            temp_file_path = temp_file.name
        print(f"[PPT] [TIME] Save temp file: {time.time() - t2:.2f}s")

        # STEP 3: Load PPT/PPTX
        t3 = time.time()
        docs = load_ppt(temp_file_path)
        print(f"[PPT] [TIME] Load presentation: {time.time() - t3:.2f}s | Documents: {len(docs)}")

        if not docs:
            raise HTTPException(status_code=400, detail="No text content could be extracted from the presentation.")

        # STEP 4: Semantic chunking
        t4 = time.time()
        embeddings = OllamaEmbeddings(model='nomic-embed-text')
        split_documents = split_text_with_semantic_chunker(docs, embeddings)
        print(f"[PPT] [TIME] Split text: {time.time() - t4:.2f}s | Chunks: {len(split_documents)}")

        # STEP 5: Save text chunks
        t5 = time.time()
        output_dir = f'temp/{client_id}_{reference_id}'
        save_documents_to_txt(split_documents, output_dir)
        print(f"[PPT] [TIME] Save text: {time.time() - t5:.2f}s")

        # STEP 6: Create embeddings
        t6 = time.time()
        create_and_save_embeddings(split_documents, client_id, reference_id)
        print(f"[PPT] [TIME] Embeddings: {time.time() - t6:.2f}s")

        # STEP 7: Merge FAISS
        t7 = time.time()
        merge_all_faiss(client_id, reference_id)
        print(f"[PPT] [TIME] Merge FAISS: {time.time() - t7:.2f}s")

        # STEP 8: Token count
        t8 = time.time()
        token_count = count_tokens_in_ppt_documents(docs)
        print(f"[PPT] [TIME] Token count: {time.time() - t8:.2f}s")

        print(f"[PPT] [TOTAL TIME]: {time.time() - start_total:.2f}s")

        return {"message": "PPT/PPTX training and embedding creation completed successfully.", "token_count": token_count}

    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Processing failed: {e}")
    finally:
        if temp_file_path and os.path.exists(temp_file_path):
            os.remove(temp_file_path)


""" FOR UNTRAIN FROM LOCAL """


import shutil

@app.post("/untrain_document_edurigo_ai")
async def delete_document(reference_id: str = Form(...), client_id: str = Form(...)):
    try:
        local_path = f"my_embeddings/{client_id}"
        file_path = os.path.join(local_path, reference_id)
        if os.path.exists(file_path):
            if os.path.isfile(file_path):
                os.remove(file_path)
            else:
                shutil.rmtree(file_path)
            return {"message": "Document untrained successfully"}
        else:
            raise HTTPException(status_code=404, detail="Document not found")
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


""" GENERATE MCQS LOCALY """

from generate_mcq import generate_mcq

class GenerateMCQRequest(BaseModel):
    client_id: int
    num_questions: int
    reference_id: int
    isGPU:int

@app.post("/generate_mcq")
async def generate_mcq_endpoint(client_id: int = Form(...), num_questions: int = Form(...), reference_id: int = Form(...), isGPU: int = Form(...)):
    mcqs, history_count = await generate_mcq(client_id, num_questions, reference_id, isGPU)
    return {"history_count": history_count, "mcqs": mcqs}


""" GENERATE MCQS FROM DOC/DOCX DOCUMENTS """

from generate_mcq_doc import generate_mcq_doc

@app.post("/generate_mcq_doc")
async def generate_mcq_doc_endpoint(client_id: int = Form(...), num_questions: int = Form(...), reference_id: int = Form(...), isGPU: int = Form(...)):
    """Generate MCQs from trained DOC/DOCX document embeddings."""
    mcqs, history_count = await generate_mcq_doc(client_id, num_questions, reference_id, isGPU)
    return {"history_count": history_count, "mcqs": mcqs}


""" GENERATE MCQS FROM PPT/PPTX PRESENTATIONS """

from generate_mcq_ppt import generate_mcq_ppt

@app.post("/generate_mcq_ppt")
async def generate_mcq_ppt_endpoint(client_id: int = Form(...), num_questions: int = Form(...), reference_id: int = Form(...), isGPU: int = Form(...)):
    """Generate MCQs from trained PPT/PPTX presentation embeddings."""
    mcqs, history_count = await generate_mcq_ppt(client_id, num_questions, reference_id, isGPU)
    return {"history_count": history_count, "mcqs": mcqs}


""" CHAT WITH EDURIGO AI """

# from generate_mcq import generate_mcq

# class GenerateMCQRequest(BaseModel):
#     client_id: int
#     num_questions: int
#     reference_id: int

# @app.post("/generate_mcq")
# async def generate_mcq_endpoint(client_id: int = Form(...), num_questions: int = Form(...), reference_id: int = Form(...)):
#     mcqs, history_count = await generate_mcq(client_id, num_questions, reference_id)
#     return {"history_count": history_count, "mcqs": mcqs}


""" CHAT WITH EDURIGO AI """

from chatting import answer_question

class ChatResponse(BaseModel):
    answer: str
    processing_time: float
    total_tokens: int
    source_documents: List[str]

class ErrorResponse(BaseModel):
    error: str

@app.post("/chat_with_edurigo_ai", response_model=ChatResponse, responses={400: {"model": ErrorResponse}, 500: {"model": ErrorResponse}})
async def chat_with_document(client_id: str = Form(...), question: str = Form(...)):
    try:
        answer, source_reference_ids, total_tokens, processing_time = answer_question(int(client_id), question)
        
        return ChatResponse(
            answer=answer,
            processing_time=processing_time,
            total_tokens=total_tokens,
            source_documents=source_reference_ids  # This is now a list of reference_ids
        )
    except FileNotFoundError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"An error occurred: {str(e)}")
    

""" Video """

from training_video import convert_video_to_audio,audio_to_text, load_and_split_document1, save_faiss_index,count_tokens_video

class TrainResponse1(BaseModel):
    message: str
    token_count: int

class ErrorResponse1(BaseModel):
    error: str

def fetch_document_with_curl1(url):
    try:
        # Use subprocess to call curl and fetch the document content with follow-redirects and fail-on-error
        result = subprocess.run(["curl", "-k", "-s", "-L", "-f", url], capture_output=True, check=True)
        return result.stdout
    except subprocess.CalledProcessError as e:
        stderr_msg = e.stderr.decode('utf-8', errors='ignore').strip() if e.stderr else str(e)
        raise HTTPException(status_code=400, detail=f"Error fetching document: {stderr_msg}")

""" def load_and_split_document1(file_path):
    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            text = file.read()
        
        text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
        chunks = text_splitter.split_text(text)
        
        documents = [Document(page_content=chunk, metadata={"source": file_path}) for chunk in chunks]
        return documents
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error processing document: {e}") """

""" def count_tokens(documents):
    return sum(len(doc.page_content.split()) for doc in documents) """

@app.post("/train_with_video_edurigo_ai", response_model=TrainResponse1, responses={400: {"model": ErrorResponse1}, 500: {"model": ErrorResponse1}})
async def process_document_video(client_id: str = Form(...), document_url: str = Form(...), reference_id: str = Form(...)):
    file_extension = os.path.splitext(document_url)[1].lower()
    if file_extension not in ['.mp4']:
        raise HTTPException(status_code=400, detail="Unsupported file type. Only .mp4 supported.")

    temp_file_path = f"temp_document{file_extension}"

    try:
        mp3_output_path = f"mp3-files/{reference_id}.mp3"
        output_file = f"{reference_id}.txt"
        faiss_save_path = f"my_embeddings_video/{client_id}/{reference_id}"
        
        # Fetch and save document
        document_content = fetch_document_with_curl1(document_url)
        with open(temp_file_path, "wb") as temp_file:
            temp_file.write(document_content)

        # Convert and process audio
        # clip = VideoFileClip(temp_file_path)
        # video_duration = clip.duration / 60  # Convert to minutes
        video_duration =5
       
        if video_duration > 15:
            raise HTTPException(status_code=400, detail="Video file too large.")

        convert_video_to_audio(temp_file_path, mp3_output_path)
        text = audio_to_text(mp3_output_path, output_file)

        # Load and split document
        final_documents = load_and_split_document1(output_file)

        if not final_documents:
            raise HTTPException(status_code=400, detail="No valid documents to process.")

        token_count = count_tokens_video(final_documents)

        # Save FAISS index
        folder_path = f"my_embeddings_video/{client_id}/{reference_id}"
        os.makedirs(folder_path, exist_ok=True)
        save_faiss_index(final_documents, faiss_save_path)

        return {"message": "Success", "token_count": token_count}
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Processing failed: {str(e)}")
    finally:
        if os.path.exists(temp_file_path):
            os.remove(temp_file_path)
        if os.path.exists(mp3_output_path):
            os.remove(mp3_output_path)


""" GENERATE Video MCQS LOCALY """

from generate_mcq_video import generate_mcq_video

class GenerateMCQRequest(BaseModel):
    client_id: int
    num_questions: int
    reference_id: int
    isGPU:int

@app.post("/generate_mcq_video")
async def generate_mcq_endpoint(client_id: int = Form(...), num_questions: int = Form(...), reference_id: int = Form(...), isGPU: int = Form(...)):
    mcqs, history_count, token_count = await generate_mcq_video(client_id, num_questions, reference_id, isGPU)
    return {"history_count": history_count, "mcqs": mcqs, "token_count": token_count}



""" CHAT WITH EDURIGO AI WITH ENHANCED USER EXPIRIENCE """

""" import json
from fastapi.responses import StreamingResponse
from chatting import answer_question_stream

class ChatResponse(BaseModel):
    answer: str
    token_count: int

async def generate_stream(client_id: int, question: str):
    async for chunk, token_count in answer_question_stream(client_id, question):
        yield f"data: {json.dumps({'chunk': chunk, 'token_count': token_count})}\n\n"

@app.post("/chat_with_edurigo_ai")
async def chat(client_id: int = Form(...), question: str = Form(...)):
    async def generate():
        async for chunk, token_count in answer_question_stream(client_id, question):
            yield f"data: {json.dumps({'chunk': chunk, 'token_count': token_count})}\n\n"
    
    return StreamingResponse(generate(), media_type="text/event-stream") """

