import json
import requests

from pydantic import BaseModel, Extra, Field
from typing import List, Optional, Union
from datetime import datetime, timedelta

from langchain.callbacks.manager import CallbackManager
from langchain.chains.openai_functions import create_structured_output_runnable
from langchain_core.runnables import Runnable
from langchain.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate
from langchain.schema import SystemMessage, AIMessage, HumanMessage, BaseMessage
from langchain.prompts.chat import BaseMessagePromptTemplate

from app.configs import settings
from app.schemas.chat import ChatAdditonal
from . import MultiArgsTool

from app.utils.logger import CustomLogger
logger = CustomLogger.get_logger()

class ShowRoomToolSchema(BaseModel):
    room_filters: list[str] = Field(description="User Provided Keywords on which we find rooms, like 'room_name', 'amenities', etc", default=None) 


class ShowRoomSchema(BaseModel):
    answer: str = Field(description="Answer to user question with details of rooms")
    followUpQuestions:List[str] = Field(description="Generate very short responses of client consisting of 2-3 words only. Examples could include 'Book now.', 'Show More rooms', or 'Need help?'")
    roomList: List[str] = Field(description="Give list of room that are available to book", default=None)
    class Config:
        extra = Extra.allow  # Allow extra properties
        arbitrary_types_allowed = True

SEARCH_ROOM_PROMPT = """You are a helpful AI assistant trained to answer questions about "Bay View Hotel" -> Rooms Details. Use the following pieces of context to answer the question. If you don't find the answer in the context, politely explain that you don't know and suggest rephrasing the question, don't try to make up an answer. Answer in the language of the user question.

Include any relevant images, but only if they are found in the context. Don't make answer too much large for user gave you view in concise way (Follow law of least action).

<AVAILABLE_ROOMS>
{context}
</AVAILABLE_ROOMS>

Format your helpful response as "markdown"."""

def create_search_tool_prompt(
    extra_prompt_messages: Optional[List[BaseMessage]] = None,
)-> ChatPromptTemplate:
    
    _prompts = extra_prompt_messages or []
    messages: List[Union[BaseMessage, BaseMessagePromptTemplate]] = [
            *_prompts,
            SystemMessagePromptTemplate.from_template(SEARCH_ROOM_PROMPT),
            HumanMessagePromptTemplate.from_template("{question}")
        ]
    return ChatPromptTemplate(messages=messages)


class ShowRoomTool:
    def __init__(self, question: str, llm: Runnable, history: List[Union[HumanMessage, AIMessage, SystemMessage]] = None) -> None:
        self.llm = llm
        self.question = question
        self.chat_hisory = history
        self.hotel_url = settings.LS_API_URL
        self.hotel_current_verion = settings.HOTEL_CURRENT_VERSION
        self.sub_hotel_id = settings.SUB_HOTEL_ID

    def __call__(self):
        return MultiArgsTool(
            name="search_or_get_hotel_rooms_info",
            description="Use this tool to Get Info abour Hotel Rooms.",
            func=self._search_rooms,
            args_schema=ShowRoomToolSchema,
            return_direct=True
        )

    def _search_rooms(self, callbacks: CallbackManager = None, **kwargs):
        chain = create_structured_output_runnable(
            llm=self.llm,
            output_schema=ShowRoomSchema,
            prompt=create_search_tool_prompt(extra_prompt_messages=self.chat_hisory)
        )

        rooms_list = self._get_rooms(**kwargs)
        context = rooms_list.__str__() if len(rooms_list) else "No Rooms Found" 
        result: ShowRoomSchema = chain.invoke(input={'question': self.question, "context": context}, config={"callbacks":callbacks})
         
        result.additional =  None
        if len(rooms_list):
            result.additional = ChatAdditonal(
                actions=["change-url", "scrollTo"],
                # createDiv= "SHOW-ROOM-LIST",
                roomList=rooms_list,
                url= "/Suites",
                scrollToElement= ".room-listing-block"
            )
        return result 
    

    def _get_rooms(self, from_date = None, to_date = None, adult=0, child=0, room_filters=None, **kwargs) -> list:
        try:
            response = requests.get(
                self.hotel_url + "/frontend/Home/get_hotel_rooms",
                headers={"current_version": self.hotel_current_verion}
            )                  
            rooms_data = json.loads(response.content.decode('utf-8-sig'))

            if response.status_code != 200 or rooms_data['status'] == 'error':
                raise Exception("Error Occur in While Room Fetching")
            
            rooms_list = []
            if room_filters: 
                rooms_list = [item for item in rooms_data['data'] if ShowRoomTool.is_has(item, room_filters)]            
            
            rooms_list = [
                {
                    "roomName": data['room_display_name'],
                    "roomImage": data['thumbnailImage'],
                    "bundleRoom": data['bundleRoom'],
                    "occupancy": data['occupancy'],
                    "bed": data['bed'],
                    "room_type_code": data['room_type_code'],
                    "room_type_id": data["room_type_id"]
                }
                for data in rooms_data['data']
            ]
        
            if not len(rooms_list):
                raise Exception("No Room Found While Filtering")

            return rooms_list
        except Exception as e:
            logger.error(f"Failed to upload file: {str(e)}")
            return []
     
    
    @staticmethod
    def is_has(item, entities):
        return any(entity.lower() in item['room_display_name'].lower() for entity in entities)

