import json
import logging
from datetime import date, timedelta

from typing import Any, AsyncIterable
from fastapi import APIRouter, HTTPException, Request
from sse_starlette.sse import EventSourceResponse

from app.agents import HotelAgent
from app.agents.tool_map import tool_map
from app.agents.tools.get_reservation import get_reservation
from app.schemas.chat import ChatRequest
from app.configs.settings import settings
from app.utils.capture_meta import capture_request_metadata
from app.utils.constants import get_hotel_details
from app.utils.store_conversation import create_conversation, save_conversation
from app.agents.tools.search_room_tool import get_available_dates
from langchain_core.callbacks.manager import atrace_as_chain_group
from app.utils.extra import build_other_hotels, compact_cart_for_llm, transcribe_audio


router = APIRouter()
logger = logging.getLogger(__name__)
    
@router.post(
    "/stream",
    summary="stream chat",
    status_code=200,
)
async def chat(args:ChatRequest, request: Request):
    
    async def send_message() -> AsyncIterable[Any]:
        try:
            HOTEL_INFO = await get_hotel_details()
            if not args.question or args.question.strip() == "" and args.audio:
                args.question = await transcribe_audio(args.audio)
                yield "#query-transcribe:" + args.question
                if not args.question or args.question.strip() == "":
                    args.question = "User sends an audio message but no text detected, might be an empty audio message, respond accordingly"

            if not args.conversation_id:
                try:
                    metadata = await capture_request_metadata(request)
                except Exception as e:
                    logger.error(f"Error capturing request metadata: {str(e)}", exc_info=True)
                    metadata = {}
                args.conversation_id = await create_conversation(args.question, request.state.HOTEL_ID, request.state.SUB_HOTEL_ID, metadata)
                    
            today = date.today().strftime("%d %B, %Y")
            max_future_booking_allowed = date.today() + timedelta(days=365)
            checkin, checkout = await get_available_dates(request.state.HOTEL_ID, request.state.SUB_HOTEL_ID)
            
            agent = HotelAgent(model="gpt-5.1", api_key=settings.OPENAI_API_KEY, conversation_id=args.conversation_id, sub_hotel_id=request.state.SUB_HOTEL_ID)

            try:
                cart_data = json.dumps(compact_cart_for_llm(args.cart_data)) if args.cart_data else ""
            except Exception as e:
                logger.error(f"Error: {str(e)}")
                cart_data = ""

            tool_call = None
            tool_response = None
            final_answer = ""
            attempt = 0
            MAX_ATTEMPTS = 5

            hotel_name = HOTEL_INFO[request.state.SUB_HOTEL_ID]["hotel_name"]

            async with atrace_as_chain_group(hotel_name,inputs={"question": args.question},metadata={"conversation_id": args.conversation_id}) as group_manager:
                while True:
                    async for token in agent.stream(input={
                        'question': args.question,
                        'hotel_id':request.state.SUB_HOTEL_ID,
                        'hotel_name': hotel_name,
                        'hotel_address': HOTEL_INFO[request.state.SUB_HOTEL_ID]["hotel_address"],
                        'hotel_email': HOTEL_INFO[request.state.SUB_HOTEL_ID]["hotel_email"],
                        'hotel_phone': HOTEL_INFO[request.state.SUB_HOTEL_ID]["hotel_phone"],
                        'other_hotels': await build_other_hotels(request.state.SUB_HOTEL_ID),
                        'date': today, 
                        'checkin': checkin,
                        'checkout': checkout,
                        'context': json.dumps(args.roomList),
                        'max_future_booking_allowed': max_future_booking_allowed,
                        'cart_data': cart_data or "cart is empty",
                        'customer_details': json.dumps(args.customer_details) if args.customer_details else "",
                    }, history=args.history, tool_response=tool_response, tool_call=tool_call, callbacks=group_manager):
                        if token == "\n":
                            token = "<br>"
                        yield token
                    
                    if agent.result.tool.value != "get_reservation_details":
                        break

                    if not agent.result.params.confirmation_number or not agent.result.params.email:
                        break

                    if attempt >= MAX_ATTEMPTS:
                        break

                    attempt += 1
                    
                    final_answer = agent.result.answer + "\n\n"
                    yield "<br>"
                    yield "<br>"
                    tool_call = agent.result.model_dump_json()
                    tool_response = str(await get_reservation(agent.result.params.confirmation_number, agent.result.params.email, request.state.HOTEL_ID, request.state.SUB_HOTEL_ID))
                

                if args.conversation_id:
                    yield "#conv_id:" + args.conversation_id

                final_answer += agent.result.answer

                async for token in tool_map(agent.result, args, checkin, checkout, request.state.HOTEL_ID, request.state.SUB_HOTEL_ID):
                    if not '#' in token and not 'carousel' in token:
                        final_answer += token
                    yield token

                await group_manager.on_chain_end({"output": final_answer})

            if args.conversation_id:
                await save_conversation(args.conversation_id, request.state.HOTEL_ID, request.state.SUB_HOTEL_ID, args.question, final_answer, args.customer_id)
        
        except Exception as e:
            logger.error(f"Error: {str(e)}", exc_info=True)
            raise HTTPException(status_code=500, detail=f"Error: {str(e)}")
    return EventSourceResponse(send_message())