import numpy as np
import json
from gtts import gTTS
import tempfile
import os
from botiverse.models import TTS
from playsound import playsound

from botiverse.models import LSTMClassifier
from botiverse.preprocessors import Vocalize, Wav2Vec, Wav2Text, BertEmbedder, Frequency, BertSentenceEmbedder
from botiverse.bots.VoiceBot.utils import voice_input


class VoiceBot():
    '''An interface for the vocalizer chatbot which simulates a call with a customer service bot.'''
    def __init__(self,  call_json_path, repr='BERT-Sentence'):
        ''' 
        Load the call data from a json file.
        :param call_json_path: The path to the json file containing the call state machine.
        '''
        with open(call_json_path, 'r') as file:
            call_json = file.read()
        self.call_data = json.loads(call_json)
        self.current_node = 'A'
        self.wav2text = Wav2Text()
        if repr == 'BERT':
            self.bert_embeddings = BertEmbedder()
        elif repr == 'BERT-Sentence':
            self.bert_sentence_embeddings = BertSentenceEmbedder()
        else:
            raise Exception(f"Invalid representation {repr}. Expected BERT or BERT-Sentence.")        
    
    def generate_speech(self, text, offline=False):
        '''Use Google's TTS or offline FastSpeech 1.0 to play speech from the given text.
        :param text: The text to be converted into speech.
        :param offline: Whether to use offline FastSpeech 1.0 to generate speech.
        '''
        if offline:
            tts = TTS()
            tts.speak(text)
        else:
            tts = gTTS(text=text, lang='en', tld="us", slow=False)
            with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_audio:
                temp_filename = temp_audio.name
                tts.save(temp_filename)
                playsound(temp_filename)

    def simulate_call(self):
        '''
        Simulate a call with a customer service bot as driven by the call state machine.
        '''
        while True:
            if self.current_node == 'Z':
                # the final state has a different structure, bot only speaks and then the call ends
                bot_message = self.call_data[self.current_node]['Bot']
                self.generate_speech(bot_message)
                break

            # 1 - get the current node's data and from that get the message the bot should speak
            node_data = self.call_data[self.current_node]
            bot_message = node_data['Bot']
            self.generate_speech(bot_message)

            # 2 - get the intent options that the bot expects from the user and classify the user's response
            options = node_data['Options']
            intents = [option['Intent'] for option in options]
            max_dur = node_data['max_duration']
            human_resp = voice_input(record_time=int(max_dur))
            human_resp = self.wav2text.transcribe(human_resp)
            selected_ind, score = self.bert_sentence_embeddings.closest_sentence(human_resp, intents, retun_ind=True)
            print(f"you said: {human_resp} and the bot decided that you meant {intents[selected_ind]} with a score of {score}")
            
            # 3 - speak according to the chosen option
            speak_message = options[selected_ind]['Speak']
            self.generate_speech(speak_message)

            # 4 - go to the next state
            self.current_node = options[selected_ind]['Next']

