from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import os
from dotenv import load_dotenv
from llama_index.core.indices.vector_store.base import VectorStoreIndex
from llama_index.vector_stores.qdrant import QdrantVectorStore
from llama_index.embeddings.fastembed import FastEmbedEmbedding
from llama_index.core import Settings
import qdrant_client
from llama_index.llms.gemini import Gemini
from llama_index.core.memory import ChatMemoryBuffer
from llama_index.readers.web import FireCrawlWebReader
from fastapi.middleware.cors import CORSMiddleware
from llama_index.core.agent import ReActAgent
from llama_index.tools.duckduckgo import DuckDuckGoSearchToolSpec
from llama_index.core.llms import ChatMessage
from composio_llamaindex import ComposioToolSet, App, Action 
from llama_index.core.agent import FunctionCallingAgentWorker
from llama_index.core.llms import ChatMessage
from llama_index.llms.openai import OpenAI
from pathlib import Path

class AgentInput(BaseModel):
    sheet_id: str
    api_key: str
    query: str




import nltk
import ssl

try:
    _create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
    pass
else:
    ssl._create_default_https_context = _create_unverified_https_context

# Set the NLTK data path explicitly
nltk.data.path.append(os.environ["NLTK_DATA"])

# Download specific NLTK datasets
nltk.download('punkt', download_dir=os.environ["NLTK_DATA"])

load_dotenv()

from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware

app = FastAPI()
#app.add_middleware(HTTPSRedirectMiddleware)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # Adjust this to your needs
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Initialize session state equivalent
state = {
    'setup_complete': False,
    'documents': None,
    'chat_history': [],
    'index': None,
    'url': "",
    'collection_name': "",
}

os.environ["GOOGLE_API_KEY"] = os.getenv("GOOGLE_API_KEY")
os.environ["COMPOSIO_API_KEY"] = os.getenv("COMPOSIO_API_KEY")

# Setup functions
def embed_setup():
    Settings.embed_model = FastEmbedEmbedding(model_name="BAAI/bge-small-en-v1.5")
    Settings.llm = Gemini(temperature=0.1, model_name="models/gemini-1.5-pro")

@app.post("/sheets_query/")
async def process_query(input_data: AgentInput):
    # Load environment variables
    load_dotenv()

    # Set up OpenAI LLM
    #os.environ["OPENAI_API_KEY"] = 
    llm = OpenAI(model='gpt-4o', api_key=input_data.api_key)

    # Set up ComposioToolSet
    composio_toolset = ComposioToolSet(api_key=os.getenv("COMPOSIO_API_KEY"), output_dir=Path("./plots/"))
    tools = composio_toolset.get_tools(apps=[App.GOOGLESHEETS, App.CODEINTERPRETER])

    # Define prefix messages
    prefix_messages = [
        ChatMessage(
            role="system",
            content=(
                "You are an AI assistant who is an expert at Google Sheets. "
                "Use Google Sheets Tool and perform the necessary operations based on query, use code interpreter to plot graphs"
                "return the plotted graph images in markdown format."
                "create a sandbox and use the codeinterpreter."
            )
        )
    ]

    # Create agent
    agent = FunctionCallingAgentWorker(
        tools=tools,
        llm=llm,
        prefix_messages=prefix_messages,
        max_function_calls=10,
        allow_parallel_tool_calls=False,
        verbose=True
    ).as_agent()

    try:
        # Process the query
        response = agent.chat(f"This is the Google Sheet ID: {input_data.sheet_id}. {input_data.query}")
        return {"response": response}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

        
def qdrant_setup():
    client = qdrant_client.QdrantClient(
        os.getenv("QDRANT_URL"),
        api_key=os.getenv("QDRANT_API_KEY"),
    )
    return client

def llm_setup():
    llm = Gemini(api_key=os.getenv("GOOGLE_API_KEY"), temperature=0.3, model_name="models/gemini-pro")
    return llm

def ingest_documents(url):
    firecrawl_reader = FireCrawlWebReader(
        api_key=os.getenv("FIRECRAWL_API_KEY"),
        mode="scrape",
    )
    documents = firecrawl_reader.load_data(url=url)
    return documents

class SetupRequest(BaseModel):
    url: str = ""
    collection_name: str

class QueryRequest(BaseModel):
    query: str

@app.post("/setup/")
async def setup(request: SetupRequest):
    
    state['url'] = request.url
    state['collection_name'] = request.collection_name

    embed_setup()
    client = qdrant_setup()
    llm = llm_setup()
    vector_store = QdrantVectorStore(client=client, collection_name=state['collection_name'])

    if state['url']:
        state['documents'] = ingest_documents(state['url'])
        state['index'] = VectorStoreIndex.from_documents(state['documents'], vector_store=vector_store)
        state['setup_complete'] = True
        return {"message": f"Documents ingested from {state['url']} and query engine setup completed successfully!"}
    else:
        state['index'] = VectorStoreIndex.from_vector_store(vector_store=vector_store)
        state['setup_complete'] = True
        return {"message": f"Query engine setup completed successfully using existing collection: {state['collection_name']}"}

@app.post("/query/")
async def query(request: QueryRequest):
    if not state['setup_complete']:
        raise HTTPException(status_code=400, detail="Please complete the setup first")
    
    memory = ChatMemoryBuffer.from_defaults(token_limit=4000)
    chat_engine = state['index'].as_chat_engine(
        chat_mode="context",
        memory=memory,
        system_prompt=(
            """You are an AI assistant for developers, specializing in technical documentation. You are also great at casual conversations.
                Your task is to provide accurate, concise, and helpful responses based on the given documentation context.
                Context information is below:
                {context_str}
                Always answer based on the information in the context and general knowledge and be precise
                Given this context, please respond to the following user query:
                {query_str}
                Your response should:
                Directly address the query using information from the context
                Include relevant code examples or direct quotes if applicable
                Mention specific sections or pages of the documentation
                Highlight any best practices or potential pitfalls related to the query
                After your response, suggest 3 follow-up questions based on the context that the user might find helpful for deeper understanding.
                
                ALWAYS SUGGEST FOLLOW UP QUESTIONS
                Your response:"""
        ),
    )
    
    response = chat_engine.chat(request.query)
    state['chat_history'].append(("User", request.query))
    state['chat_history'].append(("Assistant", str(response.response)))

    return {"response": response.response}


@app.post("/agent-search/")
async def agent_search(request: QueryRequest):
    code_tools=DuckDuckGoSearchToolSpec()
    tool = code_tools.to_tool_list()
    llm = Gemini(api_key=os.environ["GOOGLE_API_KEY"], model="models/gemini-1.5-pro")
    prefix_messages = [
        ChatMessage(
            role="system",
            content=(
                "You are now a integration agent, and what  ever you are requested, you will try to execute utilizing your toools."
            ),
        )
    ]
    agent = ReActAgent.from_tools(tool, llm=llm, verbose=True)
    response = agent.chat(request.query)
    state['chat_history'].append(("User", request.query))
    state['chat_history'].append(("Assistant", str(response.response)))

    return {"response": response.response}


@app.get("/chat-history/")
async def get_chat_history():
    return {"chat_history": state['chat_history']}



@app.post("/clear-chat/")
async def clear_chat():
    state['chat_history'] = []
    return {"message": "Chat history cleared!"}