class FacebookInterface():
    name = 'facebook'
    prefix = 'fb'
    TEXT_LENGTH_LIMIT = 320

    # Post function to handle Facebook messages
    @staticmethod
    def accept_request(request):
        # Facebook recommends going through every entry since they might send
        # multiple messages in a single call during high load.
        for entry in request['entry']:
            for raw_message in entry['messaging']:
                ts_datetime = datetime.datetime.fromtimestamp(int(raw_message['timestamp']) / 1000)
                crr_datetime = datetime.datetime.utcnow()
                diff = crr_datetime - ts_datetime
                if diff.total_seconds() < settings.GOLEM_CONFIG.get('MSG_LIMIT_SECONDS', 15):
                    # get and persist user and page ids
                    logging.debug('Incoming raw FB message: {}'.format(raw_message))
                    user_id = raw_message['sender']['id']
                    page_id = entry['id']
                    chat_id = FacebookInterface.create_chat_id(page_id, user_id)
                    meta = {"user_id": user_id, "page_id": page_id}
                    session = ChatSession(FacebookInterface, chat_id, meta=meta)
                    FacebookInterface.fill_session_profile(session)
                    # Confirm accepted message
                    FacebookInterface.post_message(session, SenderActionMessage('mark_seen'))
                    # Add it to the message queue
                    accept_user_message.delay(session.to_json(), raw_message)
                elif raw_message.get('timestamp'):
                    logging.warning("Delay {} too big, ignoring message!".format(diff))

    @staticmethod
    def chat_id_to_page_id(chat_id):
        return chat_id.split('_', maxsplit=1)[0]

    @staticmethod
    def create_chat_id(page_id, fbid):
        return "{}_{}".format(page_id, fbid)

    @staticmethod
    def get_page_token(page_id):
        if 'FB_PAGE_TOKENS' in settings.GOLEM_CONFIG:
            tokens = settings.GOLEM_CONFIG.get('FB_PAGE_TOKENS')
            if page_id not in tokens:
                raise Exception('Page id "{}" not in tokens: {}'.format(page_id, tokens))
            return tokens.get(page_id)
        elif 'FB_PAGE_TOKEN' in settings.GOLEM_CONFIG:  # there is just one page
            return settings.GOLEM_CONFIG.get("FB_PAGE_TOKEN")
        return None

    @staticmethod
    def load_profile(user_id, page_id, cache=True):

        db = get_redis()
        key = 'fb_profile_' + user_id

        if not cache or not db.exists(key):
            logging.debug('Loading fb profile...')

            url = "https://graph.facebook.com/v2.6/" + user_id
            params = {
                'fields': 'first_name,last_name,profile_pic,locale,timezone,gender',
                'access_token': FacebookInterface.get_page_token(page_id)
            }
            res = requests.get(url, params=params)
            if not res.status_code == requests.codes.ok:
                logging.error("ERROR loading FB profile! Response: {}".format(res.text))
                return {}

            db.set(key, json.dumps(res.json()), ex=3600 * 24 * 14)  # save value, expire in 14 days

        return json.loads(db.get(key).decode('utf-8'))

    @staticmethod
    def fill_session_profile(session: ChatSession):
        if not session:
            raise ValueError("Session is None")
        user_id, page_id = session.meta.get("user_id"), session.meta.get("page_id")
        profile_dict = FacebookInterface.load_profile(user_id, page_id)
        session.profile.first_name = profile_dict.get("first_name")
        session.profile.last_name = profile_dict.get("last_name")
        return session

    @staticmethod
    def post_message(session: ChatSession, response):
        fbid = session.meta.get("user_id")
        page_id = session.meta.get("page_id")

        if isinstance(response, SenderActionMessage):
            request_mode = "messages"
            response_dict = {
                'sender_action': response.action,
                'recipient': {"id": fbid},
            }
        elif isinstance(response, MessageElement):
            message_tag = response.get_message_tag()
            message = FacebookInterface.to_message(response)
            response_dict = {
                "recipient": {"id": fbid},
                "message": message,
                "messaging_type": "MESSAGE_TAG" if message_tag else "RESPONSE",
                "tag": message_tag,
            }
            request_mode = "messages"
        else:
            raise ValueError('Error: Invalid message type: {}: {}'.format(type(response), response))

        FacebookInterface._do_post(request_mode, response_dict, page_id)

    @staticmethod
    def post_setting(page_id, response):
        if isinstance(response, ThreadSetting):
            request_mode = "thread_settings"
            response_dict = FacebookInterface.to_setting(response)
            logging.debug('Sending FB setting: {}'.format(response_dict))
            FacebookInterface._do_post(request_mode, response_dict, page_id)
        else:
            raise ValueError('Error: Invalid message type: {}: {}'.format(type(response), response))

    @staticmethod
    def _do_post(request_mode, response_dict, page_id):
        prefix_post_message_url = 'https://graph.facebook.com/v2.6/me/'
        token = FacebookInterface.get_page_token(page_id)
        post_message_url = prefix_post_message_url + request_mode + '?access_token=' + token

        r = requests.post(post_message_url,
                          headers={"Content-Type": "application/json"},
                          data=json.dumps(response_dict, default=json_serialize))
        if r.status_code != 200:
            logging.error('ERROR: MESSAGE REFUSED: {}'.format(response_dict))
            logging.error('ERROR: {}'.format(r.text))
            logging.exception(r.json()['error']['message'])

    @staticmethod
    def to_setting(response):
        if isinstance(response, GreetingSetting):
            return {
                "greeting": {'text': response.message},
                "setting_type": "greeting"
            }
        elif isinstance(response, GetStartedSetting):
            return {
                "call_to_actions": [{'payload': json.dumps(response.payload, default=json_serialize)}],
                "setting_type": "call_to_actions",
                "thread_state": "new_thread"
            }
        elif isinstance(response, MenuSetting):
            return {
                "call_to_actions": [FacebookInterface.to_setting(element) for element in response.elements[:10]],
                "setting_type": "call_to_actions",
                "thread_state": "existing_thread"
            }
        elif isinstance(response, MenuElement):
            r = {
                "title": response.title,
                "type": response.type,
            }
            if response.payload:
                r['payload'] = json.dumps(response.payload, default=json_serialize)
            if response.url:
                r['url'] = response.url
            return r
        raise ValueError('Error: Invalid setting type: {}: {}'.format(type(response), response))

    @staticmethod
    def to_message(response):
        if isinstance(response, TextMessage):
            if response.buttons:
                return {
                    "attachment": {
                        "type": "template",
                        "payload": {
                            "template_type": "button",
                            "text": response.text[:FacebookInterface.TEXT_LENGTH_LIMIT],
                            "buttons": [FacebookInterface.to_message(button) for button in response.buttons]
                        }
                    }
                }
            message = {'text': response.text[:FacebookInterface.TEXT_LENGTH_LIMIT]}
            if response.quick_replies:
                message["quick_replies"] = [FacebookInterface.to_message(reply) for reply in response.quick_replies]
            return message

        elif isinstance(response, GenericTemplateMessage):
            return {
                "attachment": {
                    "type": "template",
                    "payload": {
                        "template_type": "generic",
                        "elements": [FacebookInterface.to_message(element) for element in response.elements[:10]]
                    }
                }
            }

        elif isinstance(response, AttachmentMessage):
            return {
                "attachment": {
                    "type": response.attachment_type,
                    "payload": {
                        "url": response.url
                    }
                }
            }

        elif isinstance(response, GenericTemplateElement):
            message = {
                "title": response.title,
                "image_url": response.image_url,
                "subtitle": response.subtitle,
                "item_url": response.item_url
            }
            if response.buttons:
                message["buttons"] = [FacebookInterface.to_message(button) for button in response.buttons]
            return message

        elif isinstance(response, QuickReply):
            return response.to_response()

        elif isinstance(response, Button):
            return response.to_response()

        elif isinstance(response, ListTemplate):
            return response.to_response()

        raise ValueError('Error: Invalid message type: {}: {}'.format(type(response), response))

    @staticmethod
    def send_settings(setting_list):
        for setting in setting_list:
            if 'FB_PAGE_TOKENS' in settings.GOLEM_CONFIG:
                for page_id in settings.GOLEM_CONFIG.get('FB_PAGE_TOKENS'):
                    FacebookInterface.post_setting(page_id, setting)
            elif 'FB_PAGE_TOKEN' in settings.GOLEM_CONFIG:
                FacebookInterface.post_setting("", setting)

    @staticmethod
    def processing_start(session: ChatSession):
        # Show typing animation
        FacebookInterface.post_message(session, SenderActionMessage('typing_on'))

    @staticmethod
    def processing_end(session: ChatSession):
        pass

    @staticmethod
    def state_change(state):
        pass

    @staticmethod
    def parse_message(raw_message, num_tries=1):
        if 'postback' in raw_message:
            payload = json.loads(raw_message['postback']['payload'], object_hook=json_deserialize)
            payload['_message_text'] = [{'value': None}]
            return {'entities': payload, 'type': 'postback'}
        elif 'message' in raw_message:
            if 'sticker_id' in raw_message['message']:
                return FacebookInterface.parse_sticker(raw_message['message']['sticker_id'])
            if 'attachments' in raw_message['message']:
                attachments = raw_message['message']['attachments']
                return FacebookInterface.parse_attachments(attachments)
            if 'quick_reply' in raw_message['message']:
                payload = json.loads(raw_message['message']['quick_reply'].get('payload'), object_hook=json_deserialize)
                if payload:
                    payload['_message_text'] = [{'value': raw_message['message']['text']}]
                    return {'entities': payload, 'type': 'postback'}
            if 'text' in raw_message['message']:
                return parse_text_message(raw_message['message']['text'])
        return {'type': 'undefined'}

    @staticmethod
    def parse_sticker(sticker_id):
        if sticker_id in [369239383222810, 369239343222814, 369239263222822]:
            return {'entities': {'emoji': 'thumbs_up_sign', '_message_text': None}, 'type': 'message'}

        return {'entities': {'sticker_id': sticker_id, '_message_text': None}, 'type': 'message'}

    @staticmethod
    def parse_attachments(attachments):
        entities = {
            'intent': [],
            'current_location': [],
            'attachment': [],
            '_message_text': [{'value': None}]
        }
        for attachment in attachments:
            if 'coordinates' in attachment['payload']:
                coordinates = attachment['payload']['coordinates']
                entities['current_location'].append({'value': attachment['title'], 'name': attachment['title'],
                                                     'coordinates': coordinates, 'timestamp': datetime.datetime.now()})
            if 'url' in attachment['payload']:
                url = attachment['payload']['url']
                # TODO: add attachment type by extension
                entities['attachment'].append({'value': url})
                entities['intent'].append({'value': 'attachment'})
        return {'entities': entities, 'type': 'message'} class MicrosoftInterface():
    name = 'microsoft'
    prefix = 'ms'

    @staticmethod
    def get_base_url(chat_id) -> Optional[str]:
        """
        Loads the correct endpoint url for a chat from redis.
        :param chat_id: Id of the chat.
        :return: url, throws Exception if chat_id not valid or url not found
        """
        if not chat_id:
            raise Exception('Chat id must not be null')
        redis = get_redis()
        url = redis.get('ms_service_url_' + str(chat_id))
        if url is None:
            raise Exception('Service url not found for chat id ' + str(chat_id))

        url = url.decode()
        if not url.endswith('/'):
            url += '/'
        url += 'v3/'
        return url

    @staticmethod
    def set_base_url(chat_id, url):
        if not chat_id or not url:
            raise Exception('Chat id and url must not be null')
        redis = get_redis()
        redis.set('ms_service_url_' + str(chat_id), str(url))

    @staticmethod
    def set_bot_id(chat_id, bot_id):
        """
        Because the microsoft bot api doesn't send the requests to recipient "BOT_ID",
        but to recipient "BOT_ID@nnnn" instead and this ID is required to reply,
        it is saved in redis. When MS updates their documentation about IDs this will hopefully be removed.
        :param chat_id:
        :param bot_id:
        :return:
        """
        if chat_id is None or bot_id is None:
            raise Exception('Chat id and bot id must not be null')
        redis = get_redis()
        redis.set('ms_reply_botid_' + str(chat_id), str(bot_id))

    @staticmethod
    def get_bot_id(chat_id):
        if chat_id is None:
            raise Exception('Chat id must not be null')
        redis = get_redis()
        bot_id = redis.get('ms_reply_botid_' + str(chat_id))
        if bot_id is None:
            raise Exception('Bot ID not found for chat {}'.format(str(chat_id)))
        return bot_id.decode()

    @staticmethod
    def clear():
        pass

    @staticmethod
    def load_profile(uid):
        return {'first_name': 'Tests', 'last_name': ''}

    @staticmethod
    def get_auth_token():
        """
        :returns: Auth token for Microsoft Bot API.
        """
        redis = get_redis()
        if redis.exists('ms_token'):
            return redis.get('ms_token').decode()
        else:
            url = 'https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token'
            headers = {
                "Content-Type": "application/x-www-form-urlencoded"
            }
            payload = (
                'grant_type=client_credentials' +
                '&client_id=' + settings.GOLEM_CONFIG.get('MS_BOT_ID') +
                '&client_secret=' + settings.GOLEM_CONFIG.get('MS_BOT_TOKEN') +
                '&scope=' + 'https://api.botframework.com/.default'
            )
            response = requests.post(url, data=payload, headers=headers)
            if response.status_code != 200:
                logging.error(response.text)
                response.raise_for_status()
            auth_data = response.json()
            token = auth_data['access_token']
            ex = auth_data['expires_in']
            redis.set('ms_token', str(token), ex=ex)
            return token

    @staticmethod
    def post_message(uid, chat_id, response):
        if uid and not chat_id:
            # TODO initiate conversation
            return

        message_id = get_redis().get('chat_message_id:{}'.format(chat_id))
        if message_id:
            message_id = message_id.decode()

        bot_id = MicrosoftInterface.get_bot_id(chat_id)

        payload = {
            "type": "message",
            "from": {
                "id": bot_id
            },
            "conversation": {
                "id": chat_id
            },
            "recipient": {
                "id": uid
            },
            "replyToId": message_id
        }
        payload.update(MicrosoftAdapter(chat_id).to_response(response))

        url = MicrosoftInterface.get_base_url(chat_id) + 'conversations/' + chat_id + '/activities'
        if message_id and False:  # replying to message TODO get and clear atomically or set timeout or something
            url += '/' + message_id
        headers = {
            "Authorization": "Bearer " + MicrosoftInterface.get_auth_token(),
            "Content-Type": "application/json"
        }
        logging.warning(url)
        logging.warning(payload)
        response = requests.post(url, data=json.dumps(payload), headers=headers)
        if response.status_code != 200:
            logging.warning(str(payload))
            logging.warning(response)
            logging.warning(response.text)
        response.raise_for_status()

    @staticmethod
    def send_settings(settings):
        pass

    @staticmethod
    def processing_start(uid, chat_id):
        if not chat_id:
            return
        chat_id = str(chat_id)

        payload = {
            "type": "typing",
            "from": {
                "id": MicrosoftInterface.get_bot_id(chat_id)
            },
            "conversation": {
                "id": chat_id
            }
        }
        url = MicrosoftInterface.get_base_url(chat_id) + 'conversations/' + chat_id + '/activities'
        headers = {"Authorization": "Bearer " + MicrosoftInterface.get_auth_token()}
        response = requests.post(url, data=json.dumps(payload), headers=headers)
        if response.status_code != 200:
            logging.error(response.text)
            response.raise_for_status()

    @staticmethod
    def processing_end(uid, chat_id):
        pass

    @staticmethod
    def state_change(state):
        pass

    @staticmethod
    def has_message_expired(message: dict) -> bool:
        if not (message and 'date' in message):
            logging.warning('Invalid date in message')
            return True
        received = datetime.strptime(message['timestamp'], '%Y-%m-%dT%H:%M:%s.%fZ')
        now = datetime.utcnow()

        if abs(now - received) > timedelta(seconds=settings.GOLEM_CONFIG.get('MSG_LIMIT_SECONDS')):
            logging.warning('Ignoring message, too old')
            return True
        return False

    @staticmethod
    def accept_request(body, num_tries=1) -> bool:
        if body['type'] == 'message':
            uid = body['from']['id']
            chat_id = body['conversation']['id']
            get_redis().set('chat_message_id:{}'.format(chat_id), body['id'])  # TODO
            MicrosoftInterface.set_base_url(chat_id, body['serviceUrl'])
            MicrosoftInterface.set_bot_id(chat_id, body['recipient']['id'])
            accept_user_message.delay(MicrosoftInterface.name, uid, body, chat_id=chat_id)
            return True
        return False

    @staticmethod
    def parse_message(raw, num_tries=1):
        if 'text' in raw:
            return parse_text_message(raw['text'])
        elif 'value' in raw:
            payload = raw['value']
            payload['_message_text'] = [{'value': None}]
            return {'entities': payload, 'type': 'postback'}
        return {'type': 'undefined'} class TelegramInterface:
    name = 'telegram'
    prefix = 'tg'

    @staticmethod
    def get_base_url() -> Optional[str]:
        token = settings.GOLEM_CONFIG.get('TELEGRAM_TOKEN')
        if not token:
            logging.warning('Telegram token not provided. Telegram will not work.')
            return None
        return 'https://api.telegram.org/bot{}/'.format(token)

    @staticmethod
    def init_webhooks():
        from django.urls import reverse

        base_url = TelegramInterface.get_base_url()
        if not base_url:
            return
        url = base_url + 'setWebhook'

        logging.debug('Reverse telegram is: {}'.format(reverse('telegram')))
        callback_url = settings.GOLEM_CONFIG.get('DEPLOY_URL') + reverse('telegram')

        payload = {'url': callback_url}
        response = requests.post(url, data=payload)
        if not response.json()['ok']:
            logging.warning(response.json())


    @staticmethod
    def clear():
        pass

    @staticmethod
    def load_profile(uid):
        return {'first_name': 'Tests', 'last_name': ''}

    @staticmethod
    def post_message(uid, chat_id, response):
        from golem.core.interfaces.adapter.telegram import TelegramAdapter
        base_url = TelegramInterface.get_base_url()
        if not base_url:
            return
        adapter = TelegramAdapter(chat_id)
        messages = adapter.to_response(response)
        for method, payload in messages:
            url = base_url + method
            response = requests.post(url, data=payload)
            if not response.json()['ok']:
                logging.error('Telegram request failed!')
                logging.error(response.json())
                logging.error('for method {}'.format(method))
                logging.error('message is:')
                logging.error(payload)
                return

    @staticmethod
    def answer_callback_query(query_id, chat_id, message_id):
        base_url = TelegramInterface.get_base_url()
        if not base_url:
            return
        url = base_url + 'answerCallbackQuery'
        payload = {
            'callback_query_id': str(query_id)
        }
        response = requests.post(url, data=payload)
        if not response.json()['ok']:
            logging.error('Unable to answer callback query')
            logging.error(response.json())
        # hide reply keyboard after clicking
        url = base_url + 'editMessageReplyMarkup'
        payload = {'chat_id': chat_id, 'message_id': message_id, 'reply_markup': ''}
        response = requests.post(url, data=payload)
        if not response.json()['ok']:
            logging.error('Unable to remove quick replies')
            logging.error(response.json())

    @staticmethod
    def send_settings(settings):
        pass

    @staticmethod
    def processing_start(uid, chat_id):
        base_url = TelegramInterface.get_base_url()
        if not base_url:
            return
        url = base_url + 'sendChatAction'
        payload = {
            'chat_id': chat_id,
            'action': 'typing'
        }
        response = requests.post(url, data=payload)
        if not response.json()['ok']:
            logging.warning(response.json())

    @staticmethod
    def processing_end(uid, chat_id):
        pass

    @staticmethod
    def state_change(state):
        pass

    @staticmethod
    def has_message_expired(message: dict) -> bool:
        if not (message and 'date' in message):
            logging.warning('Invalid date in message')
            return True
        received = datetime.fromtimestamp(int(message['date']))
        now = datetime.utcnow()

        if abs(now - received) > timedelta(seconds=settings.GOLEM_CONFIG['MSG_LIMIT_SECONDS']):
            logging.warning('Ignoring message, too old')
            return True
        return False

    @staticmethod
    def accept_request(body, num_tries=1) -> bool:
        if not body or 'update_id' not in body:
            logging.warning('Invalid message received: {}'.format(body))
            return False

        if 'message' in body:
            message = body['message']

            chat_id = message['chat']['id']
            uid = message['from']['id'] if 'from' in message else None  # null for group chats
            if uid and not TelegramInterface.has_message_expired(message):
                logging.debug('Adding message to queue')
                accept_user_message.delay(TelegramInterface.name, uid, body, chat_id=chat_id)
                return True
            else:
                logging.warning('No sender specified, ignoring message')
                return False
        elif 'callback_query' in body:
            callback_query = body['callback_query']
            query_id = callback_query['id']
            message = callback_query['message']
            if message:
                chat_id = message['chat']['id']
                message_id = message['message_id']
                TelegramInterface.answer_callback_query(query_id, chat_id, message_id)
            else:
                logging.error('No message in callback query, probably too old, ignoring.')
            if 'message' in callback_query:
                # unfortunately, there is no way to check the age of callback itself
                chat_id = callback_query['message']['chat']['id']
                uid = message['from']['id'] if 'from' in message else None
                accept_user_message.delay(TelegramInterface.name, uid, body, chat_id=chat_id)
                return True
        else:
            logging.warning('Unknown message type')
            return False

    @staticmethod
    def parse_message(raw, num_tries=1):
        if 'message' in raw and 'text' in raw['message']:
            return parse_text_message(raw['message']['text'])
        elif 'callback_query' in raw:
            callback_query = raw['callback_query']
            data = TelegramInterface.retrieve_callback(callback_query.get('data'))
            if data:
                payload = json.loads(data)
                payload['_message_text'] = [{'value': None}]
                return {'entities': payload, 'type': 'postback'}
        return {'type': 'undefined'}

    @staticmethod
    def persist_callback(payload) -> str:
        """
        Persists payload to be used with Telegram callbacks (which can be max. 64B)
        and returns a string that can be used to retrieve the payload from redis.
        The payload will be persisted for a week.
        :param  payload     Payload to be persisted in Redis.
        :return             Unique string associated with the payload, which can be sent to Telegram.
        """
        redis = get_redis()
        key = ''.join(random.choice(string.hexdigits) for i in range(63))
        while redis.exists(key):
            key = ''.join(random.choice(string.hexdigits) for i in range(63))
        redis.set(key, payload, ex=3600 * 24 * 7)
        return key

    @staticmethod
    def retrieve_callback(key) -> Optional[str]:
        redis = get_redis()
        if redis.exists(key):
            return redis.get(key)
        return None def get_redis():
    global _connection_pool
    global _redis
    if not _connection_pool:
        config = settings.GOLEM_CONFIG.get('REDIS')
        _connection_pool = redis.ConnectionPool(
            host=config['HOST'],
            port=config['PORT'],
            password=config['PASSWORD'],
            db=0,
            max_connections=2
        )
    if not _redis:
        _redis = redis.StrictRedis(connection_pool=_connection_pool)
    return _redis def get_elastic():
    from elasticsearch import Elasticsearch
    from django.conf import settings
    config = settings.GOLEM_CONFIG.get('ELASTIC')
    if not config:
        return None
    return Elasticsearch(config['HOST'], port=config['PORT']) class ConversationTest:
    def __init__(self, name, actions, benchmark=False):
        self.benchmark = benchmark
        self.chat_id = 'benchmark_'+str(random.randint(1, 10000000)) if benchmark else 'test'
        self.name = name
        self.actions = actions
        self.session = ChatSession(unique_id=self.chat_id, interface=TestInterface, is_logged=not self.benchmark, is_test=True)

    def run(self):
        self.init()
        stats = []
        for action in self.actions:
            stat = self.run_action(action)
            if stat:
                stats.append(stat)
        
        report = {'min':{'total':0}, 'max':{'total':0}, 'avg':{'total':0}}
        for key in ['init','parsing','processing']:
            report['min'][key] = min([s[key] for s in stats])
            report['avg'][key] = sum([s[key] for s in stats]) / len(stats)
            report['max'][key] = max([s[key] for s in stats])
            report['min']['total'] += report['min'][key]
            report['avg']['total'] += report['avg'][key]
            report['max']['total'] += report['max'][key]
        return report

    def init(self):
        self.buttons = {}
        from .dialog_manager import DialogManager
        DialogManager.clear_chat(self.session.chat_id)
        TestLog.clear()
        TestInterface.clear()
        

    def run_action(self, action):
        
        if isinstance(action, UserButtonMessage):
            payload = self.buttons.get(action.title, {}).get('payload')
            if not payload:
                raise ConversationTestException('Wanted to press button {}, but it is not present.'.format(action.title))
            action = UserPostbackMessage(payload)

        if isinstance(action, UserMessage):
            from .dialog_manager import DialogManager
            start_time = time.time() # test_id=self.name, use_logging=not self.benchmark
            dialog = DialogManager(session=self.session)
            time_init = time.time() - start_time
            start_time = time.time()
            parsed = action.get_parsed()
            time_parsing = time.time() - start_time
            start_time = time.time()
            dialog.process(parsed['type'], parsed['entities'])
            time_processing = time.time() - start_time
            return {'init':time_init, 'parsing':time_parsing, 'processing':time_processing}

        if isinstance(action, BotMessage):
            message = TestInterface.messages.pop(0) if TestInterface.messages else None
            action.check(message)
            if action.can_be_repeated:
                while TestInterface.messages:
                    peek = TestInterface.messages[0]
                    try:
                        action.check(peek)
                        print('Message matches repeatable message, popping: {}'.format(peek))
                        message = TestInterface.messages.pop(0)
                    except:
                        break
            buttons = []
            if isinstance(message, TextMessage):
                buttons = message.buttons
                buttons += message.quick_replies
            if isinstance(message, GenericTemplateMessage):
                for element in message.elements:
                    buttons += element.buttons
            for button_qr in buttons:
                if hasattr(button_qr, 'payload') and button_qr.payload:
                    button_qr.payload['_log_text'] = button_qr.title
                print('Adding button {}'.format(button_qr.title))
                self.buttons[button_qr.title] = {'payload':getattr(button_qr, 'payload', None), 'url': getattr(button_qr, 'url', None)}

        if isinstance(action, StateChange):
            state = TestInterface.states.pop(0) if TestInterface.states else None
            action.check(state)

        return None class ConversationTestRecorder:

    @staticmethod
    def record_user_message(message_type, message):
        logging.warning('Recording user {}: {}'.format(message_type, message))
        db = get_redis()
        db.lpush('test_actions', json.dumps({'type':'user', 'body':{'type':message_type,'entities':message}}, default=json_serialize))

    @staticmethod
    def record_bot_message(message):
        record = {'type':type(message).__name__}
        if isinstance(message, TextMessage):
            record['text'] = message.text
        logging.warning('Recording bot message: {}'.format(record))
        db = get_redis()
        db.lpush('test_actions', json.dumps({'type':'bot', 'body':record}))

    @staticmethod
    def record_state_change(state):
        logging.warning('Recording state change: {}'.format(state))
        db = get_redis()
        db.lpush('test_actions', json.dumps({'type':'state', 'body':state}))

    @staticmethod
    def record_start():
        logging.warning('Starting recording')
        db = get_redis()
        db.delete('test_actions')
        message = TextMessage(text="Starting to record ;)")\
            .add_button(PayloadButton(title="Stop recording", payload={"test_record": "stop"}))
        return message

    @staticmethod
    def record_stop():
        logging.warning('Stopping recording')
        deploy_url = settings.GOLEM_CONFIG.get('DEPLOY_URL', "localhost:8000")
        message = TextMessage("Done recording ;)")\
            .add_button(LinkButton(title='Get result', url=deploy_url + '/golem/test_record'))\
            .add_button(PayloadButton(title='Start again', payload={"test_record": "start"}))
        return [message]

    @staticmethod
    def get_result():
        db = get_redis()
        actions = db.lrange('test_actions', 0, -1)
        response = """from golem.core.responses import *
from golem.core.tests import *

actions = []
"""
        state = None
        for action in actions[::-1]:
            action = json.loads(action.decode('utf-8'), object_hook=json_deserialize)
            body = action['body']
            print('Action body: {}'.format(body))
            if action['type'] == 'user':
                if body['type'] == 'message':
                    text_entity = body['entities']['_message_text'][0]
                    text = text_entity['value']
                    message = 'UserTextMessage("{}")'.format(text)
                    for entity in body['entities']:
                        if entity.startswith('_'):
                            continue
                        value = body['entities'][entity][0].get('value')
                        if isinstance(value, str):
                            message += '.produces_entity("{}","{}")'.format(entity, value)
                        else:
                            message += '.produces_entity("{}")'.format(entity)
                if body['type'] == 'postback':
                    text_entity = body['entities'].get('_message_text')[0]
                    text = text_entity['value']
                    message = 'UserButtonMessage("{}")'.format(text)
                response += "\n\n"+'actions.append({})'.format(message) + "\n"
            elif action['type'] == 'bot':
                message = 'BotMessage({})'.format(body['type'])
                if 'text' in body:
                    message += '.with_text("{}")'.format(body['text'])
                response += 'actions.append({})'.format(message)
            elif action['type'] == 'state':
                if body == state:
                    continue
                response += 'actions.append(StateChange("{}"))'.format(body)
                state = body
            response += "\n"

        return response class ConversationTestException(Exception):
    def __init__(self, message):
        super(ConversationTestException, self).__init__(message) class TestLog:
    messages = []

    @staticmethod
    def clear():
        TestLog.messages = []

    @staticmethod
    def log(message):
        print(message)
        TestLog.messages.append(message)

    @staticmethod
    def get():
        return TestLog.messages class UserTextMessage(UserMessage):
    def __init__(self, text):
        self.text = text
        self.entities = {}

    def produces_entity(self, entity, value = None):
        self.entities[entity] = value
        return self

    def get_parsed(self):
        TestLog.log('Sending user message "{}".'.format(self.text))
        parsed = parse_text_message(self.text)

        for entity, values in parsed['entities'].items():
            TestLog.log('- Entity {}: {}'.format(entity, values))

        for entity in self.entities:
            expected_value = self.entities[entity]
            if entity not in parsed['entities']:
                raise ConversationTestException('- Expected entity "{}" not extracted from message "{}".'.format(entity, self.text))
            elif expected_value:
                value = parsed['entities'][entity][0]['value']
                if expected_value != value:
                    raise ConversationTestException('- Extracted "{}" instead of "{}" in entity "{}" from message "{}".'.format(value, expected_value, entity, self.text))
                TestLog.log('- Extracted expected value "{}" of entity "{}".'.format(value, entity))
            else:
                TestLog.log('- Extracted expected entity "{}".'.format(entity))

        return parsed
import datetime
import json
import logging
import time
import traceback
    import importlib
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.http.response import HttpResponse, JsonResponse
from django.shortcuts import render
from django.template import loader
from django.utils.decorators import method_decorator
from django.views import generic
from django.views.decorators.csrf import csrf_exempt
from golem.core.interfaces.facebook import FacebookInterface
from golem.core.interfaces.microsoft import MicrosoftInterface
from golem.core.interfaces.telegram import TelegramInterface
from golem.core.persistence import get_redis
from golem.core.logging.elastic import get_elastic
from golem.core.tests import ConversationTest, ConversationTestRecorder, ConversationTestException, TestLog, \
    UserTextMessage
        from pprint import pprint
        from golem.core.interfaces.google import GoogleActionsInterface
    from os import listdir
    from os.path import join
    from golem.models import User

    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return generic.View.dispatch(self, request, *args, **kwargs)

    # Post function to handle Telegram messages
    def post(self, request, *args, **kwargs):
        # Converts the text payload into a python dictionary
        request_body = json.loads(self.request.body.decode('utf-8'))
        TelegramInterface.accept_request(request_body)
        return HttpResponse()


class GActionsView(generic.View):
    def get(self, request, *args, **kwargs):
        return HttpResponse()

    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return generic.View.dispatch(self, request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        body = json.loads(self.request.body.decode('utf-8'))
        logging.info(body)
        resp = GoogleActionsInterface.accept_request(body)
        return JsonResponse(resp)


class SkypeView(generic.View):
    def get(self, request, *args, **kwargs):
        body = json.loads(self.request.body.decode('utf-8'))
        MicrosoftInterface.accept_request(body)
        return HttpResponse()

    def post(self, request, *args, **kwargs):
        body = json.loads(self.request.body.decode('utf-8'))
        MicrosoftInterface.accept_request(body)
        return HttpResponse()

    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return generic.View.dispatch(self, request, *args, **kwargs)

@login_required
def run_all_tests(request):
    modules = _get_test_modules('./tests/')

    print('Running tests: {}'.format(modules))
    tests = []
    db = get_redis()
    db.delete('test_results')
    for module in modules:
        print('Running tests "{}"'.format(module))
        tests.append(_run_test_module(module))

    db.set('test_time', str(datetime.datetime.now()))

    return JsonResponse(data=tests, safe=False)

def _get_test_modules(path):
    return sorted([f.replace('.py','') for f in listdir(path) if join(path, f).endswith('.py') and not f.startswith('_')])

@login_required
def test(request):
    db = get_redis()
    results = db.hgetall('test_results')
    results = [json.loads(results[k].decode('utf-8')) for k in sorted(list(results))] if results else []

    updated_time = db.get('test_time')
    updated_time = db.get('test_time').decode('utf-8') if updated_time else None

    status = 'passed'
    avg = {'duration':0, 'total':0, 'init':0, 'parsing':0, 'processing':0}
    passed = 0
    for test in results:
        result = test['result']
        if result['status'] == 'passed':
            passed += 1
            avg['duration'] += result['duration']
            avg['total'] += result['report']['avg']['total']
            avg['init'] += result['report']['avg']['init']
            avg['parsing'] += result['report']['avg']['parsing']
            avg['processing'] += result['report']['avg']['processing']
        elif status != 'exception':
            status = result['status']

    if passed > 0:
        for key in avg:
            avg[key] = avg[key] / passed

    context = {'tests':results, 'avg':avg, 'status':status, 'updated_time' : updated_time}
    template = loader.get_template('golem/test.html')
    return HttpResponse(template.render(context, request))

def run_test(request, name):
    benchmark = request.GET.get('benchmark', False)
    return JsonResponse(data=_run_test_module(name, benchmark=benchmark))

def run_test_message(request, message):
    return _run_test_actions('message', [UserTextMessage(message)])

def _run_test_module(name, benchmark=False):

    module = importlib.import_module('tests.'+name)
    importlib.reload(module)

    result = _run_test_actions(name, module.actions, benchmark=benchmark)
    test = {'name':name, 'result':result}
    db = get_redis()
    db.hset('test_results', name, json.dumps(test))
    return test

def _run_test_actions(name, actions, benchmark=False):
    test = ConversationTest(name, actions, benchmark=benchmark)
    start_time = time.time()
    report = None
    try:
      report = test.run()
    except Exception as e:
      log = TestLog.get()

      fatal = not isinstance(e, ConversationTestException)