# # src/ai/mcp_clinet.py
# from mcp import ClientSession
# from mcp.client.streamable_http import streamablehttp_client

# class MCPClientSingleton:
#     _session: ClientSession = None
#     _stream_ctx = None
#     _session_ctx = None

#     @classmethod
#     async def get_session(cls) -> ClientSession:
#         if cls._session is None:
#             url = "http://127.0.0.1:4000/mcp"

#             # manually enter stream context
#             cls._stream_ctx = streamablehttp_client(url)
#             read, write, _ = await cls._stream_ctx.__aenter__()

#             # manually enter session context
#             cls._session_ctx = ClientSession(read, write)
#             cls._session = await cls._session_ctx.__aenter__()

#             await cls._session.initialize()
        
#         return cls._session

#     @classmethod
#     async def close_session(cls):
#         if cls._session:
#             await cls._session_ctx.__aexit__(None, None, None)
#             cls._session = None
#         if cls._stream_ctx:
#             await cls._stream_ctx.__aexit__(None, None, None)
#             cls._stream_ctx = None


 





# import asyncio
# from mcp import ClientSession
# from mcp.client.streamable_http import streamablehttp_client

# async def main():
#     url = "http://127.0.0.1:4000/mcp"
#     async with streamablehttp_client(url) as (read, write, get_session_id):
#         async with ClientSession(read, write) as session:
#             await session.initialize()            # JSON-RPC „initialize“
#             result = await session.call_tool("add", {"a": 21, "b": 21})
#             print("Ergebnis vom Server:", result)

# if __name__ == "__main__":
#     asyncio.run(main())







from typing import Optional
from contextlib import AsyncExitStack
import traceback
import json
import os
from datetime import datetime

from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
from openai import AsyncOpenAI



import logging
import os
from logging.handlers import RotatingFileHandler

# 1. Create logs directory if it doesn’t exist
os.makedirs("logs", exist_ok=True)

# 2. Get a named logger (you can choose any name you like)
logger = logging.getLogger("mcp_client")
logger.setLevel(logging.DEBUG)  # capture DEBUG and higher for this logger

# 3. Console handler: DEBUG+
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)

console_formatter = logging.Formatter(
    "%(asctime)s %(name)s [%(levelname)s]  %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
console_handler.setFormatter(console_formatter)
logger.addHandler(console_handler)

# 4. File handler (rotating): INFO+ goes to a file
file_handler = RotatingFileHandler(
    filename="app.log",
    maxBytes=5 * 1024 * 1024,  # 5 MB per file
    backupCount=3,             # keep up to 3 old log files
)
file_handler.setLevel(logging.INFO)

file_formatter = logging.Formatter(
    "%(asctime)s %(name)s [%(levelname)s] (%(filename)s:%(lineno)d)  %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
file_handler.setFormatter(file_formatter)
logger.addHandler(file_handler)

# 5. (Optional) Silence overly‐verbose third‐party loggers
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("asyncio").setLevel(logging.WARNING)

key = "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"



# class MCPClient:
#     def __init__(self):
#         # Initialize session and client objects
#         self.session: Optional[ClientSession] = None
#         self.exit_stack = AsyncExitStack()
#         self.llm = AsyncOpenAI(base_url="https://api.groq.com/openai/v1",api_key=key)
#         self.tools = []
#         self.messages = []
#         self.logger = logger

#     async def connect_to_server(self, server_url: str):
#         """
#         Connects to an MCP server over HTTP (streamable HTTP transport).
#         """
#         try:
#             # Establish a streamable HTTP transport to the MCP server
#             stream_transport = await self.exit_stack.enter_async_context(
#                 streamablehttp_client(server_url)
#             )
#             # stream_transport is a 3-tuple: (read, write, get_session_id)
#             self.stdio, self.write, _ = stream_transport

#             # Open an MCP ClientSession over that transport
#             self.session = await self.exit_stack.enter_async_context(
#                 ClientSession(self.stdio, self.write)
#             )
#             await self.session.initialize()
#             self.logger.info("Connected to MCP server")

#             # Fetch the list of available tools from the MCP server
#             mcp_tools = await self.get_mcp_tools()
#             self.tools = [
#                 {
#                     "type": "function",
#                     "function":{
#                     "name": tool.name,
#                     "description": tool.description,
#                     "parameters": tool.inputSchema,
#                     }      
#                 }
#                 for tool in mcp_tools
#             ]
#             print(self.tools)
#             self.logger.info(
#                 f"Available tools: {[tool['function']['name'] for tool in self.tools]}"

#             )
#             return True

#         except Exception as e:
#             self.logger.error(f"Error connecting to MCP server: {e}")
#             traceback.print_exc()
#             raise

#     async def get_mcp_tools(self):
#         """
#         Retrieves the list of tools exposed by the MCP server.
#         """
#         try:
#             response = await self.session.list_tools()
#             return response.tools
#         except Exception as e:
#             self.logger.error(f"Error getting MCP tools: {e}")
#             raise

#     async def process_query(self, query: str):
#         """
#         Sends a user query to the LLM. If the LLM returns a tool call,
#         execute it against the MCP server, feed the result back into the conversation,
#         and repeat until the LLM replies with pure text.
#         """
#         try:
#             self.logger.info(f"Processing query: {query}")
#             self.messages = [{"role": "user", "content": query}]
#             count = 0
#             while count <= 2:
#                 count = count + 1
#                 response = await self.call_llm()
#                 print("LLM response from while block",response.choices[0])
#                 choice = response.choices[0]
#                 message = choice.message  # This is an OpenAI ChatMessage object

#                 # If the model decided to call a tool...
#                 if hasattr(message, "tool_calls") and message.tool_calls:
#                     for tool_call in message.tool_calls:
#                         tool_name = tool_call.function.name
#                         tool_args = json.loads(tool_call.function.arguments)
#                         tool_use_id = tool_call.id

#                         self.logger.info(
#                             f"Calling tool {tool_name} with args {tool_args}"
#                         )
#                         try:
#                             result = await self.session.call_tool(tool_name, tool_args)
#                             print("tool calling result",result)
#                             result = result.content[0].text
#                             print("tool calling result",result)
#                         except Exception as e:
#                             self.logger.error(f"Error calling tool {tool_name}: {e}")
#                             raise

#                         # Append the tool’s output as a “tool_result” message
#                         self.messages.append(
#                             {
#                                 "tool_call_id": tool_use_id,
#                                 "role": "tool",
#                                 "name": tool_name,
#                                 "content": result,
#                             }
#                         )
#                         await self.log_conversation()

#                     # After feeding all tool results, loop again so the LLM sees them
#                     continue

#                 # Otherwise, it’s a plain assistant response—append and finish
#                 assistant_text = message.content
#                 self.messages.append({"role": "assistant", "content": assistant_text})
#                 await self.log_conversation()
#                 break

#             return self.messages

#         except Exception as e:
#             self.logger.error(f"Error processing query: {e}")
#             raise

#     async def call_llm(self):
#         """
#         Calls OpenAI’s chat completion endpoint with the current conversation and tool definitions.
#         """
#         try:
#             self.logger.info("Calling LLM")
#             response = await self.llm.chat.completions.create(
#                 model="meta-llama/llama-4-scout-17b-16e-instruct",
#                 messages=self.messages,
#                 tools=self.tools,
#                 max_tokens=1000,
#             )
#             print("LLM Response",response.choices[0].message.content)
#             return response

#         except Exception as e:
#             self.logger.error(f"Error calling LLM: {e}")
#             traceback.print_exc()
#             raise

#     async def cleanup(self):
#         """
#         Closes the HTTP connection and cleans up resources.
#         """
#         try:
#             await self.exit_stack.aclose()
#             self.logger.info("Disconnected from MCP server")
#         except Exception as e:
#             self.logger.error(f"Error during cleanup: {e}")
#             traceback.print_exc()
#             raise

#     async def log_conversation(self):
#         """
#         Writes the current conversation (self.messages) to a timestamped JSON file
#         under the `conversations/` directory.
#         """
#         os.makedirs("conversations", exist_ok=True)
#         serializable_conversation = []

#         for message in self.messages:
#             try:
#                 serializable_message = {"role": message["role"], "content": []}

#                 if isinstance(message["content"], str):
#                     serializable_message["content"] = message["content"]

#                 elif isinstance(message["content"], list):
#                     for content_item in message["content"]:
#                         if hasattr(content_item, "to_dict"):
#                             serializable_message["content"].append(
#                                 content_item.to_dict()
#                             )
#                         elif hasattr(content_item, "dict"):
#                             serializable_message["content"].append(content_item.dict())
#                         elif hasattr(content_item, "model_dump"):
#                             serializable_message["content"].append(
#                                 content_item.model_dump()
#                             )
#                         else:
#                             serializable_message["content"].append(content_item)

#                 serializable_conversation.append(serializable_message)

#             except Exception as e:
#                 self.logger.error(f"Error processing message: {str(e)}")
#                 self.logger.debug(f"Message content: {message}")
#                 raise

#         timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
#         filepath = os.path.join("conversations", f"conversation_{timestamp}.json")

#         try:
#             with open(filepath, "w") as f:
#                 json.dump(serializable_conversation, f, indent=2, default=str)
#         except Exception as e:
#             self.logger.error(f"Error writing conversation to file: {str(e)}")
#             self.logger.debug(f"Serializable conversation: {serializable_conversation}")
#             raise




# async def main():
#     client = MCPClient()
#     await client.connect_to_server("http://127.0.0.1:4000/mcp")
#     await client.process_query("add 10 and 20")
#     await client.cleanup()

# if __name__ == "__main__":
#     import asyncio
#     asyncio.run(main())




import backoff
from openai import AsyncOpenAI, OpenAIError, RateLimitError
import  asyncio

client = AsyncOpenAI(api_key=key)

@backoff.on_exception(backoff.expo, (RateLimitError, OpenAIError), max_tries=10)
async def do_mcp_call():
    resp = await client.responses.create(
        model="gpt-4.1-2025-04-14",
        tools=[{
            "type": "mcp",
            "server_label": "Demo-Server",
            "server_url": "https://9d0b-115-98-41-61.ngrok-free.app/mcp",
            # "headers": {"Authorization": "Bearer YOUR_MCP_TOKEN"},
            "require_approval": "never"
        }],
        input="I would like to know the total revenue and a detailed breakdown of the monthly revenue trend. Which month has the highest revenue also Would you like a detailed month-by-month breakdown or a visual chart of the monthly trend",
    )
    return resp.output_text

async def main():
    try:
        result = await do_mcp_call()
        print("MCP result:", result)
    except Exception as e:
        print("Failed:", e)

asyncio.run(main())




# You are an expert in interpreting and simplifying ambiguous or complex queries, focusing on query decomposition and asking clarifying questions when necessary. Your goal is to break down user queries into clearly understandable components, identify missing context, and request clarification when the query is ambiguous or lacks necessary details.

# Instructions:

# Rewriting User Query:

# Simplify and clarify the user’s query for better readability and understanding.

# Retain the original structure and intent of the query, avoiding any additions like “you want” or “you need.” Ensure that the query remains as close to the original as possible while making it easier to understand.

# Query Decomposition:

# Break down the query into its core components, ensuring all relevant details (e.g., tables, columns, actions, or attributes) are addressed.

# Identify Missing Information:

# If any part of the query is ambiguous, ask for clarification.

# If essential details (e.g., time frame, specific filters, or constraints) are missing, ask the user for that context before proceeding.

# Response Structure in JSON:

# Provide a clear and structured response in JSON format, with the following properties:

# simplified_query: The rewritten and simplified version of the user's query.

# query_components: A breakdown of the query into its core components.

# clarification_needed: A boolean flag indicating if clarification is required (true or false).

# clarification: If clarification is required, provide specific questions for the user. If no clarification is needed, this should be an empty array [].

# Keep it Contextually Relevant:

# Ensure the response remains contextually correct, especially when dealing with dates, time periods, or other specific data queries.
