from typing import Any
from langchain import hub
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain_core.messages import AIMessage, HumanMessage
from langchain.globals import set_debug, set_verbose
from openai import OpenAIError

from qwin_bot_domain import QwinBotConversationContext, ChainCallbacks
from qwin_bot_config import bot_config
from qwin_bot_session import SessionDict
from qwin_bot_tools import QwinBotToolsCreator

from qdrant import QwinQdrantClient


def _chat_response(output_seq):
    return 0, output_seq


def error_response(e):
    return 1, f'Error: {e}'


def opeanai_error_response(e):
    return 2, f'OpenAPI error: {e}'


def operator_response():
    return -1, ''


class QWinBot:
    def __init__(self, verbose: bool = False):
        set_debug(verbose)
        set_verbose(verbose)
        
        self.config = bot_config()
        
        self.session = SessionDict(verbose=verbose)
        
        self.llm = ChatOpenAI(model=self.config['openai']['model'], 
                              openai_api_key=self.config['openai']['api_key'],
                              temperature=.0)
        
        qdrant = QwinQdrantClient()
        self.initial_params = {
            'game_list': qdrant.game_list()
        }
        
        self.tools_creator = QwinBotToolsCreator(self.llm, self.initial_params)
        
        self.prompt = hub.pull("hwchase17/openai-functions-agent")
        self.prompt.messages[0].prompt.template = f'''You are a helpful assistant of QWin online casino.
        QWin is crypto currency casino, supports many crypto currencies, has many different games: 
        card games, table games, videopokers, slots.
        
        rules:
        - DO NOT ANSWER that you are OpenAI bot, AI etc. You are QWin assistant!
        - For answer USE ONLY USER LAST QUESTION LANGUAGE, the language of user question. If you cannot define user language clearly - use English.
        - Do not mention any provided internal content, answer as if you know it yourself. 
        - Do not provide game symbol codes, use only game symbol names
        - Do not translate the name of the games, always use the provided name in English
        
        Internal coin - qwinB, 1 qwinB = 0.0001 BTC
        
        {self.initial_params["game_list"]}
        '''
        
    def invoke(self, user_id: str, query: str, verbose: bool=False) -> tuple[int, str] | tuple[int, Any] | Any:
        try: 
            conv_context = self.session.get(user_id)
            if not conv_context:
                conv_context = QwinBotConversationContext(user_id=user_id)
                self.session.set(user_id, conv_context)
                
            if conv_context.need_operator:
                return operator_response()
                
            conv_context.chat_history.append(HumanMessage(content=query))
            
            tools = self.tools_creator.qwin_bot_tools(user_id, conv_context)
            agent = create_openai_functions_agent(self.llm, tools, self.prompt)
            self.executor = AgentExecutor(
                agent=agent,
                tools=tools,
                return_intermediante_steps=True,
                verbose=verbose
            )
            callbacks = ChainCallbacks()
            
            response = self.executor.invoke({'input': query,
                                             'chat_history': list(conv_context.chat_history)},
                                            {'callbacks': [callbacks]})
            output = response['output']

            conv_context.chat_history.append(AIMessage(content=output))
            self.session.set(user_id, conv_context)
            
            # if output == 'operator':
            #     conv_context.need_operator = True
            #     self.session.set(user_id, conv_context)
            #     return self.operator_response()
                
            return _chat_response(output)

        except OpenAIError as e:
            if verbose:
                print(e)
                
            return opeanai_error_response(e)
        except Exception as e:
            if verbose:
                print(e)
                
            return error_response(e)

