#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
import json
import re
import uuid
from base64 import decodebytes
from base64 import urlsafe_b64encode
from datetime import date
from datetime import datetime
from datetime import timedelta
from typing import Dict
from zlib import compress

import requests
from aws_xray_sdk.core import xray_recorder as xray

from iobot.views import ModalView
from iobot.slack import Agent  # noqa: F401
from iobot.slack import SlackStatement
from iobot.utils import redirect_stderr_to_stdout

from psi_logger import logger

# TODO: move these to plugin architecture - https://mathieularose.com/plugin-architecture-in-python
# from SlackEventsCore.slater import Accounts
# from SlackEventsCore.slater import SLater
# from SlackEventsCore.slater.auth import SLaterAuth
# from SlackEventsCore.slater.utils import convert_finops_aws_metadata_to_slater_import_sheets  # noqa: E501
# from SlackEventsCore.slater.utils import convert_sheets_to_zip_bundle
# from SlackEventsCore.slater.utils import get_sheets_from_workbook



@xray.capture()
@logger.log
def get_message(event, log, session):
    if 'Records' in event:
        for pos, record in enumerate(event["Records"]):
            # put_metadata(f"record-{pos}", record)
            body = decodebytes(bytes(record["body"], 'utf-8'))
            slack_event = json.loads(body)
            log.debug(f"slack_event: {slack_event}")
            return slack_event  # for now just the first instance...


@xray.capture()
@logger.log
def Utterances(event_type: str = "", check_exists: bool = False, get_handler: bool = False, get_help: bool = False):
    """ Utterances is an accessor to functionality supported by this
    router. You can use it to validate an event_type, retrieve its logical
    controller, or its help.

    Parameters
    ----------
    event_type : str
      A string that represents a routable action that you want to validate,
      retrieve, or understand.
      When combined with get_help, only the help for the single handler will
      be returned.

    check_exists : bool
      Set True and event_type will be checked against the internal dict
      resulting in a True/False evaluation.

    get_handler : bool
      Set True and event_type handler will be retrieved (if it exists)
      from the internal dict resulting in a routable action.
      When an event_type is deprecated, the handler for it's 'deprecated_by'
      value will be returned.

    get_help : bool
      Set True and the internal dictionary will be returned.
      When combined with event_type, only the single handler's help is returned.

    Returns
    -------
    bool : When check_exists is used
    FunctionWrapper : When get_handler is used
    Dictionary : When get_help is used
    None : When options are used incorrectly or in case of invalid event_type

    When provided a valid event, validate we returns True for check_exists
    >>> Utterances(event_type='app_home_opened',check_exists=True)
    True

    When provided a invalid event, validate we return False for check_exists
    >>> Utterances(event_type='bogus_event',check_exists=True)
    False

    When get_handler for a valid event is called, return the handler
    >>> Utterances(event_type='app_home_opened',get_handler=True).__name__
    'publish_home_page_view'

    When get_handler for a invalid event is called, return None
    >>> type(Utterances(event_type='bogus_event',get_handler=True))
    <class 'NoneType'>

    When get_handler for help is called w/o an event type return all handlers
    >>> Utterances(get_help=True).get('app_home_opened').get('description')
    'Auto-triggers when a user clicks on the home tab for this app'

    When get_handler for help is called with an event type return that handler
    >>> Utterances(event_type='app_home_opened', get_help=True).get('description')
    'Auto-triggers when a user clicks on the home tab for this app'

    When get_handler for help is called with a deprecated event type return that handler
    >>> Utterances(event_type='export_qualys_agent_report', get_help=True).get('description')
    'Will return a menu of qualys agent compliance report interactions'

    When get_handler for a deprecated function is called and
    return_deprecated_handler is True, return the default deprecated warning
    handler.
    >>> Utterances(event_type='export_qualys_agent_report',get_handler=True).__name__
    'deprecated_handler_message'

    Patch Utterances() function
    >>> UTTERANCE_HANDLERS['export_qualys_agent_report']['deprecation']['return_deprecated_handler'] = False

    When get_handler for a deprecated function is called and return_deprecated_handler is False, return the handler
    for the deprecated_by configuration
    >>> Utterances(event_type='export_qualys_agent_report',get_handler=True).__name__
    'export_qualys_agent_report_v2'

    When no options are set, return None:
    >>> type(Utterances(event_type='bogus_event'))
    <class 'NoneType'>

    When all options are set, return None:
    >>> type(Utterances(event_type='bogus_event',check_exists=True,get_handler=True,get_help=True))
    <class 'NoneType'>
    """
    # annotate("event_type", event_type)
    # annotate("check_exist", str(check_exists))
    # annotate("get_handler", str(get_handler))
    # annotate("get_help", str(get_help))

    if not check_exists and not get_handler and not get_help:
        return None

    if check_exists and get_handler and get_help:
        return None

    handlers = UTTERANCE_HANDLERS

    if get_help and not event_type:
        return handlers

    if get_help and event_type:
        if event_type in handlers:
            handler = handlers[event_type]
            if handler['deprecation'].get('is_deprecated'):
                return handlers[handler['deprecation']['deprecated_by']]
            else:
                return handlers[event_type]

    if check_exists:
        return event_type in handlers

    if get_handler:
        if event_type in handlers:
            # annotate("matched_handler", "yes")
            if handlers[event_type]['deprecation'].get('is_deprecated'):
                # annotate("is_deprecated_controller",
                #          handlers[event_type]['deprecation'].get('is_deprecated'))
                if handlers[event_type]['deprecation'].get('return_deprecated_handler'):
                    return handlers['deprecated_handler_message']['handler']
                else:
                    return handlers[handlers[event_type]['deprecation']['deprecated_by']]['handler']
            else:
                return handlers[event_type]['handler']
        else:
            return None


@xray.capture()
@logger.log
def get_intention_type(statement: SlackStatement, default) -> str:
    intention_event_type: str = default

    if statement.get('plain_text').startswith('iobot-cli'):
        intention_event_type = "iobot_cli"

    elif 'archive please' in statement.get('plain_text'):
        intention_event_type = "process_archive_file_upload_request"

    elif ('slater import please' in statement.get('plain_text') or 'slater finops import please' in statement.get('plain_text')):
        intention_event_type = "slater_import_upload_request"

    elif 'qualys report please' in statement.get('plain_text'):
        # TODO: replace with deprecated handler
        intention_event_type = "export_qualys_agent_report"

    elif any([all(list(re.search(r'\b%s\b' % word, statement.get('plain_text')) for word in ['qualys', 'agent', 'report', 'please'])),
              all(list(re.search(r'\b%s\b' % word, statement.get('plain_text'))
                       for word in ['qualys', 'instance', 'report', 'please'])),
              all(list(re.search(r'\b%s\b' % word, statement.get('plain_text')) for word in ['qualys', 'compliance', 'data', 'please']))]):
        intention_event_type = "export_qualys_agent_report_v2"

    elif any([all(list(re.search(r'\b%s\b' % word, statement.get('plain_text')) for word in ['qualys', 'vuln', 'report', 'please'])),
              all(list(re.search(r'\b%s\b' % word, statement.get('plain_text'))
                       for word in ['qualys', 'vulnerabilities', 'report', 'please'])),
              all(list(re.search(r'\b%s\b' % word, statement.get('plain_text')) for word in ['qualys', 'vuln', 'data', 'please']))]):
        intention_event_type = "export_qualys_vulnerabilities_report"

    elif 'cloudwatch logs for' in statement.get('plain_text'):
        intention_event_type = "export_cloudwatch_logs"

    elif all(list(re.search(r'\b%s\b' % word, statement.get('plain_text')) for word in ['execute', 'on', 'as', 'please'])):
        intention_event_type = "execute_jenkins_job"

    elif 'account lookup' in statement.get('plain_text'):
        intention_event_type = "slater_account_lookup"

    elif 'accounttype lookup' in statement.get('plain_text'):
        intention_event_type = "slater_account_type_lookup"

    elif 'application lookup' in statement.get('plain_text'):
        intention_event_type = "slater_application_lookup"

    elif 'applicationcontact lookup' in statement.get('plain_text'):
        intention_event_type = "slater_application_contact_lookup"

    elif 'businessunit lookup' in statement.get('plain_text'):
        intention_event_type = "slater_business_unit_lookup"

    elif 'contact lookup' in statement.get('plain_text'):
        intention_event_type = "slater_contact_lookup"

    elif 'contacttype lookup' in statement.get('plain_text'):
        intention_event_type = "slater_contact_type_lookup"

    elif 'lob lookup' in statement.get('plain_text'):
        intention_event_type = "slater_lob_lookup"

    elif 'masterpayer lookup' in statement.get('plain_text'):
        intention_event_type = "slater_master_payer_lookup"

    elif 'rootemail lookup' in statement.get('plain_text'):
        intention_event_type = "slater_root_email_lookup"

    elif 'service lookup' in statement.get('plain_text'):
        intention_event_type = "slater_service_lookup"

    elif 'team lookup' in statement.get('plain_text'):
        intention_event_type = "slater_team_lookup"

    elif 'tower lookup' in statement.get('plain_text'):
        intention_event_type = "slater_tower_lookup"

    elif any([re.search(r'^%s$' % 'help', statement.get('plain_text')),
              'help please' in statement.get('plain_text')]):
        intention_event_type = "general_utterances_help"

    else:
        intention_event_type = "route_to_lex"

    return intention_event_type


@xray.capture()
@logger.log
def publish_home_page_view(slack_event, log, session, msg_agent, slack_token, intention_event_type) -> Dict:
    """ Test generating a home page view
    >>> os.environ['STACK_NAME'] = 'iobots-stack'
    >>> testing.export_secret_arns(os.environ['STACK_NAME'])
    >>> context = {}

    Mimic a home page open event
    >>> event_file = open('events/open_home_page_event.json', 'r')
    >>> event = json.loads(event_file.read())
    >>> message = get_message(event, log, boto_session)
    >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())
    >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
    >>> response = publish_home_page_view(message['event'],log, boto_session, agent, testing.slack_bearer_token(), '')
    >>> assert (response['ok'] == True), f'expected True, got {response["ok"]}'
    >>> testing.unexport_secret_arns()
    >>> del os.environ['STACK_NAME']
    """
    log.debug("publishing view")
    log.debug(f'user: {slack_event.get("user")}')
    slater = SLater(log, session, requests.Session())
    hp = slater.GetSLaterHomePageView(log, msg_agent.client)

    if 'view' in slack_event:
        pub_response = hp.publish(slack_event.get(
            "user"), hash_id=slack_event.get('view').get('hash'))
    else:
        pub_response = hp.publish(slack_event.get("user"), "")
    return {'ok': pub_response.get("ok")}

# TODO: move this to a lambda handler that is 'routed'


@xray.capture()
@logger.log
def process_archive_file_upload_request(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
    log.debug("processing file upload request")
    text_types = ['json', 'csv', 'txt', 'xml']

    if 'files' in slack_event:
        archive_channels_list: list = []
        processed_channels_from_files: int = 0
        for item in slack_event['files']:
            log.debug(f"processing: {item['title']}, type: {item['filetype']}")
            file_ref: str = item['url_private']
            http_session = requests.Session()

            headers: Dict[str, str] = {
                "Authorization": f"Bearer {slack_token}"
            }
            response: Dict = http_session.get(file_ref, headers=headers)
            if response.status_code == 200:
                if item["filetype"] in text_types:
                    file_content = response.content.decode("utf-8")
                    channel_index = slack_client.list_channels()
                    rows = file_content.splitlines()

                    for line_number, row in enumerate(rows, start=1):
                        if row.startswith("#") or row.startswith("\n"):
                            log.debug(
                                f"skipping comment or blank line: {line_number}")
                            continue

                        cols = row.rstrip().split(",")
                        max = len(cols) - 1
                        if cols[max].lower() == 'archive':
                            processed_channels_from_files += 1
                            log.debug(f"archiving: {cols[0]}")
                            for channel in channel_index:
                                if (line_number == 1 and cols[0] == 'Name'):
                                    log.debug(
                                        f"skipping header line {line_number}")
                                    continue

                                if (channel['name'] == cols[0]):
                                    archive_channels_list.append(channel["id"])
                        else:
                            processed_channels_from_files += 1

                    data = json.dumps(archive_channels_list).encode('utf-8')
                    encoded = urlsafe_b64encode(compress(data)).decode('utf-8')
                    view = [
                        {
                            'type': 'section',
                            'text': {
                                'type': 'mrkdwn',
                                'text': ':star::star: *_Super_* *_Duper_* *_Magical_* *_Channel_* *_Archive-O-Rator_* :five::zero::zero::zero: :tm: :star::star:'
                            }
                        },
                        {
                            'type': 'section',
                            'text': {
                                'type': 'mrkdwn',
                                'text': f'Are you *sure* you want to proceed with ghosting :ghost: *_{len(archive_channels_list)}_* (of {processed_channels_from_files}) channel(s)?'
                            }
                        },
                        {
                            'type': 'actions',
                            'block_id': 'archive_channels',
                            'elements': [
                                {
                                    'type': 'button',
                                    'action_id': 'archive_channels',
                                    'text': {
                                        'type': 'plain_text',
                                        'text': 'Yes',
                                        'emoji': True
                                    },
                                    'value': f'{encoded}',
                                    'style': 'primary'
                                },
                                {
                                    'type': 'button',
                                    'text': {
                                        'type': 'plain_text',
                                        'text': 'No',
                                        'emoji': True
                                    },
                                    'style': 'danger',
                                    'value': 'denied'
                                }
                            ]
                        },
                        {
                            'type': 'context',
                            'elements': [
                                {
                                    'type': 'mrkdwn',
                                    'text': f'*Proccessed from:* <{file_ref}|{item["title"]}> by <@{slack_event.get("user")}>'
                                }
                            ]
                        }
                    ]
        state = slack_client.send(
            "", slack_event.get("channel"), "", blocks=view)
        log.debug(f"state: {state}")
        return {'ok': state.get('ok')}
    else:
        state = slack_client.send("", slack_event.get("channel"), "", blocks={
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': 'Looks like you want to archive channels? If so, say that again with a file attached'
            }
        })
        log.debug(f"state: {state}")
        return {'ok': state.get('ok')}


# TODO: move these to plugin architecture - https://mathieularose.com/plugin-architecture-in-python
# @xray.capture()
# @logger.log
# def slater_import(slack_event, log, session, msg_agent, slack_token, intention_event_type) -> Dict:
#     """ Imports attached spread sheets into SLater

#     Setup test environment
#     >>> os.environ['STACK_NAME'] = 'iobots-stack'

#     >>> testing.export_secret_arns(os.environ['STACK_NAME'])

#     >>> event_file = open('events/iobot_incoming_router_slater_import_event.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

#     >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

#     >>> response = slater_import(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(),'slater_import_upload_request')

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}

#     >>> event_file = open('events/iobot_incoming_router_slater_finops_import_event.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> response = slater_import(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(), 'slater_import_upload_request')

#     Message should look per usual submittal
#     >>> response
#     {'ok': True}

#     >>> testing.unexport_secret_arns()
#     >>> del os.environ['STACK_NAME']
#     """
#     log.debug(f"processing file upload request: {slack_event}")

#     statement: SlackStatement = SlackStatement(slack_event.get('text'))
#     log.debug(f"statement: {statement}")

#     text_types = ['csv', 'xlsx']
#     state = {'ok': False}
#     if 'files' in slack_event and len(slack_event['files']) > 0:
#         for item in slack_event['files']:
#             log.debug(f"processing: {item['title']}, type: {item['filetype']}")
#             # annotate("file_title", f"{item['title']}")
#             # annotate("file_name", f"{item['filetype']}")
#             file_ref: str = item['url_private']
#             # annotate("file_url", f"{item['url_private']}")

#             http_session = requests.Session()
#             headers: Dict[str, str] = {
#                 "Authorization": f"Bearer {slack_token}"}
#             response: Dict = http_session.get(file_ref, headers=headers)
#             if response.status_code == 200:
#                 if item["filetype"] in text_types:
#                     file_content = response.content
#                     # annotate("file_size", f"{sys.getsizeof(file_content)}")
#                     zip_data: bytes

#                     try:
#                         if 'finops' in statement.get('words'):
#                             # annotate("slater_import_type", "finops")
#                             log.debug(
#                                 f"converting finops data to slater format {item['title']}")
#                             zip_data = convert_sheets_to_zip_bundle(
#                                 convert_finops_aws_metadata_to_slater_import_sheets(file_content))
#                         else:
#                             # annotate("slater_import_type", "native")
#                             log.debug(
#                                 f"processing native slater import {item['title']}")

#                             zip_data = convert_sheets_to_zip_bundle(
#                                 get_sheets_from_workbook(file_content))

#                             log.debug(f'zip_data: {zip_data.getvalue()}')
#                     except Exception as e:
#                         state = msg_agent.send("", slack_event.get("channel"), "", blocks=[{
#                             'type': 'section',
#                             'text': {
#                                     'type': 'mrkdwn',
#                                     'text': f'Failed to import data to SLater: {e}'
#                             }
#                         }])
#                         log.debug(f"state: {state}")
#                         return {'ok': state.get('ok')}

#                     slack_user_id = slack_event.get("user")
#                     entitlement_level = SLaterAuth(
#                         log, msg_agent, slack_user_id).GetEntitlement()
#                     log.debug(
#                         f'user entitlement level set to: {entitlement_level}')

#                     slater = SLater(
#                         log, session, http_session, slater_user=slack_user_id, entitlement_level=entitlement_level)
#                     import_response = slater.CreateBatchImport(
#                         group_batch_zip=zip_data, slater_user=slack_user_id
#                     )
#                     log.debug(f'import response: {import_response}')
#                     slater_import_uuid = import_response.get(
#                         'group_batch_uuid')

#                     import_status = slater.GetBatchImportStatus(group_batch_uuid=uuid.UUID(
#                         slater_import_uuid), slater_user=slack_event.get("user"))
#                     ref = import_response.get('next_link')
#                     # annotate('group_batch_uuid', slater_import_uuid)
#                     # annotate('slater_import_ref', ref)

#                     view = [
#                         {
#                             'type': 'section',
#                             'text': {
#                                 'type': 'mrkdwn',
#                                 'text': ':star::star: *_Super_* *_Duper_* *_Magical_* :ac_slater: *_SLater-Import-O-Rator_* :five::zero::zero::zero: :tm: :star::star:'
#                             }
#                         },
#                         {
#                             'type': 'divider'
#                         }
#                     ]

#                     for batch in import_status:
#                         view.append(
#                             {
#                                 'type': 'section',
#                                 'block_id': f'slater_bulk_import_data-{batch.get("group_batch_load_id")}',
#                                 'fields': [
#                                     {
#                                         'type': 'mrkdwn',
#                                         'text': f'*Status:* {batch.get("status")}'
#                                     },
#                                     {
#                                         'type': 'mrkdwn',
#                                         'text': f'*ID:* {batch.get("group_batch_load_id")}'
#                                     },
#                                     {
#                                         'type': 'mrkdwn',
#                                         'text': f'*Loaded:* {batch.get("load_date")}'
#                                     },
#                                     {
#                                         'type': 'mrkdwn',
#                                         'text': f'*Processed:* {str("Pending...") if not batch.get("processed_date") else batch.get("processed_date")}'
#                                     },
#                                     {
#                                         'type': 'mrkdwn',
#                                         'text': f'*Errors:* {"0" if not batch.get("num_errors") else batch.get("num_errors")}'
#                                     },
#                                     {
#                                         'type': 'mrkdwn',
#                                         'text': '*Last Refreshed:* N/A'
#                                     }
#                                 ]
#                             })

#                     view.append({
#                         'type': 'actions',
#                         'block_id': 'slater_bulk_import_control',
#                         'elements': [
#                             {
#                                 'type': 'button',
#                                 'action_id': 'slater_bulk_import_details',
#                                 'text': {
#                                     'type': 'plain_text',
#                                     'text': 'Details',
#                                     'emoji': True
#                                 },
#                                 'value': f'{slater_import_uuid}^{ref}^{None}',
#                                 'style': 'danger'
#                             },
#                             {
#                                 'type': 'button',
#                                 'action_id': 'slater_bulk_import_refresh',
#                                 'text': {
#                                         'type': 'plain_text',
#                                         'text': 'Refresh',
#                                         'emoji': True
#                                 },
#                                 'value': f'{slater_import_uuid}^{ref}^{None}',
#                                 'style': 'primary'
#                             }
#                         ]
#                     })

#                     view.append({
#                         'type': 'divider'
#                     })

#                     view.append({
#                         'type': 'context',
#                         'elements': [
#                                 {
#                                     'type': 'mrkdwn',
#                                     'text': f'*Proccessed {slater_import_uuid} from:* <{file_ref}|{item["title"]}> by <@{slack_event.get("user")}>'
#                                 }
#                         ]
#                     }
#                     )
#                     state = msg_agent.send(
#                         "", slack_event.get("channel"), "", blocks=view)
#                 else:
#                     state = msg_agent.send("", slack_event.get("channel"), "", blocks=[{
#                         'type': 'section',
#                         'text': {
#                             'type': 'mrkdwn',
#                             'text': f'Sorry, I only support the following types {", ".join(text_types)}. Please try again with one of those attached'
#                         }
#                     }])
#             else:
#                 # send a message about the issue to chatops-notifications
#                 state = msg_agent.send("", "GSURR8Q8J", "", blocks=[
#                     {
#                         'type': 'context',
#                         'elements': [
#                             {
#                                 'type': 'mrkdwn',
#                                 'text': f'*Debug:* *Proccessed {slater_import_uuid} from:* <{file_ref}|{item["title"]}> *by* <@{slack_event.get("user")}>\n{ref}'
#                             }
#                         ]
#                     },
#                     {
#                         'type': 'section',
#                         'text': {
#                             'type': 'mrkdwn',
#                             'text': f'I encountered a problem while processing the above event. Should I invite <@{slack_event.get("user")}> to this channel to discuss or set a reminder so you can investigate first? BTW, I told them I would contact you.'
#                         }
#                     },
#                     {
#                         'type': 'actions',
#                         'block_id': 'slater_support_import_incident',
#                         'elements': [
#                             {
#                                 'type': 'button',
#                                 'action_id': 'slater_support_import_incident_triage',
#                                 'text': {
#                                     'type': 'plain_text',
#                                     'text': 'Yes',
#                                     'emoji': True
#                                 },
#                                 'value': f'{slater_import_uuid}^{ref}^{slack_event.get("user")}',
#                                 'style': 'primary'
#                             },
#                             {
#                                 'type': 'button',
#                                 'action_id': 'slater_bulk_import_incident_cancel',
#                                 'text': {
#                                     'type': 'plain_text',
#                                     'text': 'No',
#                                     'emoji': True
#                                 },
#                                 'value': f'cancel^{slater_import_uuid}^{ref}^{slack_event.get("user")}',
#                                 'style': 'alert'
#                             }
#                         ]
#                     }
#                 ])
#                 state = msg_agent.send("", slack_event.get("channel"), "", blocks=[{
#                     'type': 'section',
#                     'text': {
#                         'type': 'mrkdwn',
#                         'text': 'Failed to fetch or process that file, the support team has been notified to contact you.'
#                     }
#                 }])
#     else:
#         state = msg_agent.send("", slack_event.get("channel"), "", blocks=[{
#             'type': 'section',
#             'text': {
#                 'type': 'mrkdwn',
#                 'text': 'Looks like you want to import data to SLater? If so, say that again with a file attached'
#             }
#         }])
#     log.debug(f"state: {state}")
#     return {'ok': state.get('ok')}

# # TODO: move this to a lambda handler that is 'routed'


@xray.capture()
@logger.log
def export_qualys_agent_report(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
    """Fetch qualys report data from S3

    Setup test environment
    >>> os.environ['STACK_NAME'] = 'iobots-stack'

    >>> testing.export_secret_arns(os.environ['STACK_NAME'])

    >>> event_file = open('events/iobot_incoming_router_deprecated_event.json', 'r')

    >>> event = json.loads(event_file.read())

    >>> message = get_message(event, log, boto_session)

    >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

    >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

    >>> response = export_qualys_agent_report(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(), 'export_qualys_agent_report')

    Validate we have dispatched OK
    >>> response
    {'ok': True}

    >>> testing.unexport_secret_arns()

    >>> del os.environ['STACK_NAME']
    """
    date_matrix: Dict = {
        "today": date.today(),
        "latest": date.today(),
        "recent":  date.today() - timedelta(days=1),
        "yesterday":  date.today() - timedelta(days=1),
        "few": date.today() - timedelta(days=3)
    }
    log.debug("processing qualys report request")
    statement: SlackStatement = SlackStatement(slack_event.get('text'))

    timeframe: float
    timematch = [value for key, value in date_matrix.items(
    ) if key.lower() in statement.get('plain_text')]
    if len(timematch) == 0:
        timeframe = date.today()
    else:
        timeframe = timematch[0]

    view = [
        {
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': ':star::star: *_Super_* *_Duper_* *_Magical_* *_Qualys_* *_Report_* *_Gen-O-Rator_* :five::zero::zero::zero: :tm: :star::star:'
            }
        },
        {
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': f'Did you want me to fetch a qualys report for the date \'{timeframe}\'?'
            }
        },
        {
            'type': 'actions',
            'block_id': 'fetch_qualys_agent_report',
            'elements': [
                {
                    'type': 'button',
                    'action_id': 'fetch_qualys_report_basic',
                    'text': {
                        'type': 'plain_text',
                        'text': 'Yes: Basic CSV',
                        'emoji': True
                    },
                    'value': f'basic^{timeframe}',
                    'style': 'primary'
                },
                {
                    'type': 'button',
                    'action_id': 'fetch_qualys_report_detailed',
                    'text': {
                        'type': 'plain_text',
                        'text': 'Yes: Detailed CSV',
                        'emoji': True
                    },
                    'value': f'detailed^{timeframe}',
                    'style': 'primary'
                },
                {
                    'type': 'button',
                    'action_id': 'fetch_qualys_report_render',
                    'text': {
                        'type': 'plain_text',
                        'text': 'Yes: Top 10 w/o Agents Chart',
                        'emoji': True
                    },
                    'value': f'chart^{timeframe}',
                    'style': 'primary'
                },
                {
                    'type': 'button',
                    'action_id': 'fetch_qualys_report_cancel',
                    'text': {
                        'type': 'plain_text',
                        'text': 'No',
                        'emoji': True
                    },
                    'style': 'danger',
                    'value': 'canceled'
                }
            ]
        },
        {
            'type': 'context',
            'elements': [
                {
                    'type': 'mrkdwn',
                    'text': f'*Requested by* <@{slack_event.get("user")}>'
                }
            ]
        }
    ]
    state = slack_client.send("", slack_event.get("channel"), "", blocks=view)
    log.debug(f"state: {state}")
    return {'ok': state.get('ok')}


@xray.capture()
@logger.log
def export_qualys_vulnerabilities_report(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
    log.debug("processing qualys vulnerability data export request")
    statement: SlackStatement = SlackStatement(slack_event.get('text'))
    # annotate('raw_statement', statement.get('raw'))
    # annotate('mentionless', statement.get('mentionless'))
    words: list = statement.get('words')
    # annotate('number_of_words', str(len(words)))
    # annotate('words', ','.join(words))
    params: Dict = {'id': '', 'submitted': str(datetime.now(
    )), 'environment': '', 'account': '', 'requestor': slack_event.get("user")}

    if len(words) >= 5:
        t_appid: str = words[4]
        # annotate('t_appid', t_appid)
        params['id'] = t_appid

    if len(words) >= 7:
        environment: str = words[6]
        # annotate('environment', environment)
        params['environment'] = environment

    if len(words) >= 9:
        account: str = words[8]
        # annotate('account', account)
        params['account'] = account

    data = json.dumps(params).encode('utf-8')
    encoded = urlsafe_b64encode(compress(data)).decode('utf-8')

    view = [
        {
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': ':star::star: *_Super_* *_Duper_* *_Magical_* *_Qualys_* *_Vulnerabilities_* *_Gen-O-Rator_* :five::zero::zero::zero: :tm: :star::star:'
            }
        },
        {
            'type': 'actions',
            'block_id': 'export_qualys_vulnerabilities_report',
            'elements': [
                {
                    'type': 'datepicker',
                    'action_id': f'qualys_vuln_date_chooser^{encoded}',
                    'initial_date': f'{date.today()}',
                    'placeholder': {
                        'type': 'plain_text',
                        'text': 'Select a date',
                        'emoji': True
                    }
                }
            ]
        }
    ]
    state = slack_client.send("", slack_event.get("channel"), "", blocks=view)
    log.debug(f"state: {state}")
    return {'ok': state.get('ok')}


@xray.capture()
@logger.log
def export_qualys_agent_report_v2(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
    """Fetch qualys agent report data from Athena

    Setup test environment
    >>> os.environ['STACK_NAME'] = 'iobots-stack'

    >>> testing.export_secret_arns(os.environ['STACK_NAME'])

    >>> event_file = open('events/iobot_incoming_router_qualys_agent_report_v2_event.json', 'r')

    >>> event = json.loads(event_file.read())

    >>> message = get_message(event, log, boto_session)

    >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

    >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

    >>> response = export_qualys_agent_report_v2(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(), 'export_qualys_agent_report_v2')

    Validate we have dispatched OK
    >>> response
    {'ok': True}

    >>> testing.unexport_secret_arns()

    >>> del os.environ['STACK_NAME']
    """
    log.debug("processing qualys agent compliance data export request")
    statement: SlackStatement = SlackStatement(slack_event.get('text'))
    # annotate('raw_statement', statement.get('raw'))
    # annotate('mentionless', statement.get('mentionless'))
    words: list = statement.get('words')
    # annotate('number_of_words', str(len(words)))
    # annotate('words', ','.join(words))

    log.debug(f"number_of_words: {str(len(words))}")
    log.debug(f"words: {','.join(words)}")

    # annotate('number_of_words', str(len(words)))
    # annotate('words', ','.join(words))
    params: Dict = {'id': '', 'submitted': str(datetime.now(
    )), 'environment': '', 'account': '', 'requestor': slack_event.get("user")}

    if len(words) >= 5:
        t_appid: str = words[4]
        # annotate('t_appid', t_appid)
        params['id'] = t_appid

    if len(words) >= 7:
        environment: str = words[6]
        # annotate('environment', environment)
        params['environment'] = environment

    if len(words) >= 9:
        account: str = words[8]
        # annotate('account', account)
        params['account'] = account

    data = json.dumps(params).encode('utf-8')
    encoded = urlsafe_b64encode(compress(data)).decode('utf-8')

    view = [
        {
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': ':star::star: *_Super_* *_Duper_* *_Magical_* *_Qualys_* *_Agent Compliance_* *_Gen-O-Rator_* :five::zero::zero::zero: :tm: :star::star:'
            }
        },
        {
            'type': 'actions',
            'block_id': 'export_qualys_agent_detailed_report',
            'elements': [
                {
                    'type': 'button',
                    'action_id': 'fetch_qualys_detailed_report_render',
                    'text': {
                        'type': 'plain_text',
                        'text': 'Yes: Top 10 w/o Agents Chart',
                        'emoji': True
                    },
                    'value': f'chart^{date.today()}^{encoded}',
                    'style': 'primary'
                },
                {
                    'type': 'datepicker',
                    'action_id': f'qualys_agent_date_detailed_chooser^{encoded}',
                    'initial_date': f'{date.today()}',
                    'placeholder': {
                        'type': 'plain_text',
                        'text': 'Select a date',
                        'emoji': True
                    }
                }
            ]
        }
    ]
    state = slack_client.send("", slack_event.get("channel"), "", blocks=view)
    log.debug(f"state: {state}")
    return {'ok': state.get('ok')}


@xray.capture()
@logger.log
def export_cloudwatch_logs(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
    date_matrix: Dict = {
        "today": date.today(),
        "last_five_minutes": datetime.now() - timedelta(minutes=5),
        "last_fifteen_minutes": datetime.now() - timedelta(minutes=15),
        "last_one_hour": datetime.now() - timedelta(hours=1),
        "yesterday":  date.today() - timedelta(days=1),
        "few": date.today() - timedelta(days=3)
    }
    log.debug("processing cloudwatch log export request")
    statement: SlackStatement = SlackStatement(slack_event.get('text'))
    # annotate('raw_statement', statement.get('raw'))
    # annotate('mentionless', statement.get('mentionless'))
    words: list = statement.get('words')
    # annotate('number_of_words', str(len(words)))
    # annotate('words', ','.join(words))

    log_group: str = words[3]
    # annotate('log_group', log_group)

    account: str = words[5]
    # annotate('account', account)

    view = [
        {
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': ':star::star: *_Super_* *_Duper_* *_Magical_* *_Cloudwatch_* *_Log_* *_Gen-O-Rator_* :five::zero::zero::zero: :tm: :star::star:'
            }
        },
        {
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': f'Account: {account}, Log Group: {log_group}\nChoose a quick pick or select a date:'
            }
        },
        {
            'type': 'actions',
            'block_id': 'export_cloudwatch_logs',
            'elements': [
                {
                    'type': 'button',
                    'action_id': 'cloudwatch_logs_last_five_minutes',
                    'text': {
                        'type': 'plain_text',
                        'text': '5 Minutes',
                        'emoji': True
                    },
                    'value': f'{date_matrix["last_five_minutes"]}^{datetime.now()}',
                    'style': 'primary'
                },
                {
                    'type': 'button',
                    'action_id': 'cloudwatch_logs_last_fifteen_minutes',
                    'text': {
                        'type': 'plain_text',
                        'text': '15 Minutes',
                        'emoji': True
                    },
                    'value': f'{account}^{log_group}^{date_matrix["last_fifteen_minutes"]}^{datetime.now()}',
                    'style': 'primary'
                },
                {
                    'type': 'button',
                    'action_id': 'cloudwatch_logs_last_one_hour',
                    'text': {
                        'type': 'plain_text',
                        'text': '1 Hour',
                        'emoji': True
                    },
                    'value': f'{account}^{log_group}^{date_matrix["last_one_hour"]}^{datetime.now()}',
                    'style': 'primary'
                },
                {
                    'type': 'button',
                    'action_id': 'cloudwatch_logs_for_today',
                    'text': {
                        'type': 'plain_text',
                        'text': 'Today',
                        'emoji': True
                    },
                    'value': f'{account}^{log_group}^{date_matrix["today"]}^{datetime.now()}',
                    'style': 'primary'
                },
                {
                    'type': 'button',
                    'action_id': 'cloudwatch_logs_for_yesterday',
                    'text': {
                        'type': 'plain_text',
                        'text': 'Yesterday',
                        'emoji': True
                    },
                    'value': f'{account}^{log_group}^{date_matrix["yesterday"]}^{datetime.now()}',
                    'style': 'primary'
                },
                {
                    'type': 'datepicker',
                    'action_id': f'cloudwatch_logs_date_chooser^{account}^{log_group}',
                    'initial_date': f'{date_matrix["today"]}',
                    'placeholder': {
                        'type': 'plain_text',
                        'text': 'Select a date',
                        'emoji': True
                    }
                }
            ]
        },
        {
            'type': 'context',
            'elements': [
                {
                    'type': 'mrkdwn',
                    'text': f'*Requested by* <@{slack_event.get("user")}>'
                }
            ]
        }
    ]
    state = slack_client.send("", slack_event.get("channel"), "", blocks=view)
    log.debug(f"state: {state}")
    return {'ok': state.get('ok')}


@xray.capture()
@logger.log
def execute_jenkins_job(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
    """ Test executing a simple jenkins job

    >>> skip('Need to sort out event for test')
    >>> os.environ['STACK_NAME'] = 'iobots-stack'
    >>> testing.export_secret_arns(os.environ['STACK_NAME'])
    >>> context = {}

    Mimic a home page open event
    >>> event_file = open('events/execute_jenkins_job_event.json', 'r')
    >>> event = json.loads(event_file.read())
    >>> message = get_message(event, log, boto_session)
    >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())
    >>> config = {'slack_signing_secret_arn': os.environ["SLACK_SIGNING_SECRET_ARN"], 'slack_verification_token_arn': os.environ['VERIFICATION_TOKEN_ARN'], 'slack_bearer_token_arn': os.environ['SLACK_BEARER_TOKEN_ARN'], 'destination_queue': os.environ['DESTINATION_QUEUE_URL'], 'pagerduty_token_arn': os.environ['PAGERDUTY_TOKEN_ARN'] }
    >>> agent = Agent(log,boto_session,slack_client,config,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)
    >>> response = execute_jenkins_job(message,log, boto_session, agent, testing.slack_bearer_token(),'')
    >>> assert (response['ok'] == True), f'expected True, got {response["ok"]}'
    >>> testing.unexport_secret_arns()
    >>> del os.environ['STACK_NAME']
    """
    log.debug(f"processing execute jenkins job request: {slack_event}")
    statement: SlackStatement = SlackStatement(slack_event.get('text'))
    # annotate('raw_statement', statement.get('raw'))
    # annotate('mentionless', statement.get('mentionless'))
    words: list = statement.get('words')
    # annotate('number_of_words', str(len(words)))
    # annotate('words', ','.join(words))

    job_name: str = ""
    jenkins_host: str = ""
    credentials: str = ""

    job_name = words[1]
    # annotate('job_name', job_name)

    jenkins_host = words[3]
    # annotate('jenkins_host', jenkins_host)

    credentials = words[5]
    # annotate('credentials', credentials)

    timeframe: float = date.today()

    view = [
        {
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': ':star::star: *_Super_* *_Duper_* *_Magical_* *_Jenkins_* *_Job-O-Rator_* :five::zero::zero::zero: :tm: :star::star:'
            }
        },
        {
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': f'Execute jenkins job "{job_name}" on "{jenkins_host}" as <@{slack_event.get("user")}> using creds "{credentials}" on date "{timeframe}"?'
            }
        },
        {
            'type': 'actions',
            'block_id': 'execute_jenkins_job',
            'elements': [
                {
                    'type': 'button',
                    'action_id': 'execute_jenkins_job_confirm',
                    'text': {
                        'type': 'plain_text',
                        'text': 'Yes',
                        'emoji': True
                    },
                    'value': f'{job_name}^{jenkins_host}^{credentials}',
                    'style': 'primary'
                },
                {
                    'type': 'button',
                    'action_id': 'execute_jenkins_job_cancel',
                    'text': {
                        'type': 'plain_text',
                        'text': 'No',
                        'emoji': True
                    },
                    'style': 'danger',
                    'value': 'canceled'
                }
            ]
        }
    ]
    state = slack_client.send("", slack_event.get("channel"), "", blocks=view)
    log.debug(f"state: {state}")
    return {'ok': state.get('ok'), 'event': slack_event}


@xray.capture()
@logger.log
def dispatch(event, log, session, destination_queue, slack_event):
    # annotate("destination_queue", destination_queue)
    log.debug(f"using queue: {destination_queue}")
    encoded_body = urlsafe_b64encode(json.dumps(
        slack_event).encode('utf-8')).decode('utf-8')
    log.debug(f"encoded_body: {encoded_body}")

    queue = session.client('sqs')
    response = queue.send_message(
        QueueUrl=destination_queue,
        MessageBody=str(encoded_body),
    )

    log.debug(f"invoke response: {response}")
    log.debug(f"body: {slack_event}")
    return slack_event

# TODO: move these to plugin architecture - https://mathieularose.com/plugin-architecture-in-python
# @xray.capture()
# @logger.log
# def slater_account_lookup(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
#     """SLater Account Lookup

#     Setup test environment
#     >>> os.environ['STACK_NAME'] = 'iobots-stack'

#     >>> testing.export_secret_arns(os.environ['STACK_NAME'])

#     >>> event_file = open('events/iobot_incoming_router_slater_account_lookup_one_account_dev.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

#     >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

#     >>> response = slater_account_lookup(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(), 'slater_account_lookup')

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}

#     >>> event_file = open('events/iobot_incoming_router_slater_account_lookup_prefix.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> response = slater_account_lookup(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(), 'slater_account_lookup')

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}

#     >>> testing.unexport_secret_arns()

#     >>> del os.environ['STACK_NAME']
#     """
#     log.debug("processing slater account lookup")
#     statement: SlackStatement = SlackStatement(slack_event.get('text'))
#     words: list = statement.get('words')

#     if len(words) >= 3:
#         query_key: str = " ".join(words[2:])

#     http_session = requests.Session()
#     slater = SLater(log, session, http_session)

#     if query_key.isdigit():
#         results = slater.GetAccountsByIDPrefix(query_key, limit=5)
#     elif query_key.isalnum() and any(x in query_key for x in ['@']):
#         results = slater.GetAccounts(filter_prefix=query_key,
#                                      filter_field_name="cloud_account_root_contact",  # noqa: E501
#                                      limit=5)
#     else:
#         results = slater.GetAccounts(filter_prefix=query_key,
#                                      filter_field_name="cloud_account_name",  # noqa: E501
#                                      limit=5)

#     modal: ModalView = None
#     found: int = len(results)
#     if found > 0:
#         if found == 1:
#             contacts = slater.GetContactsByCloudAccountNumber(
#                 results[0].get('cloud_account_number'))
#             log.debug(f'contacts: {contacts}')

#             applications = slater.GetApplicationsByCloudAccountNumber(
#                 results[0].get('cloud_account_number').lower())
#             log.debug(f'accounts: {applications}')

#             modal = slater.GetCloudDeliveryServicesAccountDetailView(
#                 log, slack_client.client, data=results[0], contacts=contacts,
#                 applications=applications)

#             log.debug(f'result: {results[0]}')

#         else:
#             modal = slater.GetSLaterAccountsListView(
#                 log, slack_client.client, data=results)
#             log.debug(f'results: {results}')

#     else:
#         modal = slater.GetSLaterSlashCommandNoResultsView(log,
#                                                           slack_client.client,
#                                                           f"No results for your query '{query_key}', please try again...")

#     message = modal.to_slack_message().data.get('blocks')
#     state = slack_client.send(
#         "", slack_event.get("channel"), "", blocks=message)

#     return {'ok': state.get('ok')}


# TODO: move these to plugin architecture - https://mathieularose.com/plugin-architecture-in-python
# @xray.capture()
# @logger.log
# def slater_account_type_lookup(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
#     """ Return lookup details

#     Setup test environment
#     >>> os.environ['STACK_NAME'] = 'iobots-stack'

#     >>> testing.export_secret_arns(os.environ['STACK_NAME'])

#     >>> slater = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

#     >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

#     >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

#     >>> intention: str = "slater_account_type_lookup"

#     >>> primary_key: str = "account_type_name"

#     >>> resource: str = "accounttype lookup"

#     >>> data = slater.GetAccountTypes(limit=1)

#     >>> len(data) == 1
#     True

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_account_type_lookup(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(), intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid_dev.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_account_type_lookup(message.get('event'),
#     ...                                      log, boto_session, agent,
#     ...                                      testing.slack_bearer_token(),
#     ...                                      intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}

#     >>> testing.unexport_secret_arns()

#     >>> del os.environ['STACK_NAME']
#     """
#     statement: SlackStatement = SlackStatement(slack_event.get('text'))
#     words: list = statement.get('words')
#     if len(words) >= 3:
#         query_key: str = words[2]

#     http_session = requests.Session()
#     slater = SLater(log, session, http_session)

#     if query_key.isdigit():
#         results = slater.GetAccountTypesByNamePrefix(query_key, limit=5)
#     else:
#         results = slater.GetAccountTypesByIDPrefix(query_key, limit=5)
#     modal: ModalView = None
#     found: int = len(results)
#     if found > 0:
#         if found == 1:
#             modal = slater.GetSLaterAccountTypeDetailView(
#                 log, slack_client.client, data=results[0])
#             log.debug(f'result: {results[0]}')
#         else:
#             modal = slater.GetSLaterAccountTypesListView(
#                 log, slack_client.client, data=results)
#             log.debug(f'results: {results}')
#     else:
#         modal = slater.GetSLaterSlashCommandNoResultsView(log,
#                                                           slack_client.client,
#                                                           f"No results for your query '{query_key}', please try again...")

#     message = modal.to_slack_message().data.get('blocks')
#     state = slack_client.send(
#         "", slack_event.get("channel"), "", blocks=message)

#     return {'ok': state.get('ok')}

# TODO: move these to plugin architecture - https://mathieularose.com/plugin-architecture-in-python
# @xray.capture()
# @logger.log
# def slater_application_lookup(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
#     """ Return application lookup details

#     Setup test environment
#     >>> os.environ['STACK_NAME'] = 'iobots-stack'

#     >>> testing.export_secret_arns(os.environ['STACK_NAME'])

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

#     >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

#     >>> response = slater_application_lookup(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(),'slater_application_lookup')

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid_dev.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> response = slater_application_lookup(message.get('event'),
#     ...                                      log, boto_session, agent,
#     ...                                      testing.slack_bearer_token(),
#     ...                                      'slater_application_lookup')

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}
#     """
#     log.debug("processing slater application lookup")
#     statement: SlackStatement = SlackStatement(slack_event.get('text'))
#     words: list = statement.get('words')

#     if len(words) >= 3:
#         query_key: str = " ".join(words[2:])

#     http_session = requests.Session()
#     slater = SLater(log, session, http_session)

#     if query_key.isdigit() or query_key.lower().startswith('svc'):
#         results = slater.GetApplicationsByTAppIDPrefix(query_key, limit=5)
#     else:
#         results = slater.GetApplications(filter_prefix=query_key, limit=5)

#     modal: ModalView = None
#     found: int = len(results)
#     if found > 0:
#         if found == 1:
#             contacts = slater.GetContactsByApplicationID(
#                 results[0].get('application_id'))
#             log.debug(f'contacts: {contacts}')

#             t_appid: str = results[0].get('t_appid')
#             accounts: Accounts = None
#             if t_appid:
#                 accounts = slater.GetAccountsByTAppID(t_appid.lower())

#             log.debug(f'accounts: {accounts}')

#             modal = slater.GetCloudDeliveryServicesApplicationDetailView(
#                 log, slack_client.client, data=results[0], contacts=contacts, accounts=accounts)
#             log.debug(f'result: {results[0]}')
#         else:
#             modal = slater.GetSLaterApplicationsListView(
#                 log, slack_client.client, data=results)
#             log.debug(f'results: {results}')

#     else:
#         modal = slater.GetSLaterSlashCommandNoResultsView(log,
#                                                           slack_client.client,
#                                                           f"No results for your query '{query_key}', please try again...")

#     message = modal.to_slack_message().data.get('blocks')
#     state = slack_client.send(
#         "", slack_event.get("channel"), "", blocks=message)

#     return {'ok': state.get('ok')}

# TODO: move these to plugin architecture - https://mathieularose.com/plugin-architecture-in-python
# @xray.capture()
# @logger.log
# def slater_application_contact_lookup(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
#     """ Return lookup details

#     Setup test environment
#     >>> os.environ['STACK_NAME'] = 'iobots-stack'

#     >>> testing.export_secret_arns(os.environ['STACK_NAME'])

#     >>> slater = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

#     >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

#     >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

#     >>> intention: str = "slater_application_contact_lookup"

#     >>> primary_key: str = "contact_name"

#     >>> resource: str = "applicationcontact lookup"

#     >>> data = slater.GetApplicationContacts(limit=1)

#     >>> len(data) == 1
#     True

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_application_contact_lookup(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(), intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid_dev.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_application_contact_lookup(message.get('event'),
#     ...                                      log, boto_session, agent,
#     ...                                      testing.slack_bearer_token(),
#     ...                                      intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}
#     """
#     statement: SlackStatement = SlackStatement(slack_event.get('text'))
#     words: list = statement.get('words')
#     if len(words) >= 3:
#         query_key: str = words[2]
#         # annotate('query_key', query_key)

#     http_session = requests.Session()
#     slater = SLater(log, session, http_session)
#     results = slater.GetApplicationContactsByNamePrefix(query_key, limit=5)

#     modal: ModalView = None
#     found: int = len(results)
#     if found > 0:
#         if found == 1:
#             modal = slater.GetSLaterApplicationContactDetailView(
#                 log, slack_client.client, data=results[0])
#             log.debug(f'result: {results[0]}')
#         else:
#             modal = slater.GetSLaterApplicationContactsListView(
#                 log, slack_client.client, data=results)
#             log.debug(f'results: {results}')
#     else:
#         modal = slater.GetSLaterSlashCommandNoResultsView(log,
#                                                           slack_client.client,
#                                                           f"No results for your query '{query_key}', please try again...")

#     message = modal.to_slack_message().data.get('blocks')
#     state = slack_client.send(
#         "", slack_event.get("channel"), "", blocks=message)

#     return {'ok': state.get('ok')}


# TODO: move these to plugin architecture - https://mathieularose.com/plugin-architecture-in-python
# @xray.capture()
# @logger.log
# def slater_business_unit_lookup(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
#     """ Return lookup details

#     Setup test environment
#     >>> os.environ['STACK_NAME'] = 'iobots-stack'

#     >>> testing.export_secret_arns(os.environ['STACK_NAME'])

#     >>> slater = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

#     >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

#     >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

#     >>> intention: str = "slater_business_unit_lookup"

#     >>> primary_key: str = "business_unit_name"

#     >>> resource: str = "businessunit lookup"

#     >>> data = slater.GetBusinessUnits(limit=1)

#     >>> len(data) == 1
#     True

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_business_unit_lookup(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(), intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid_dev.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_business_unit_lookup(message.get('event'),
#     ...                                      log, boto_session, agent,
#     ...                                      testing.slack_bearer_token(),
#     ...                                      intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}
#     """
#     statement: SlackStatement = SlackStatement(slack_event.get('text'))
#     words: list = statement.get('words')
#     if len(words) >= 3:
#         query_key: str = words[2]
#         # annotate('query_key', query_key)

#     http_session = requests.Session()
#     slater = SLater(log, session, http_session)
#     results = slater.GetBusinessUnitsByNamePrefix(query_key, limit=5)

#     modal: ModalView = None
#     found: int = len(results)
#     if found > 0:
#         if found == 1:
#             modal = slater.GetSLaterBusinessUnitDetailView(
#                 log, slack_client.client, data=results[0])
#             log.debug(f'result: {results[0]}')
#         else:
#             modal = slater.GetSLaterBusinessUnitsListView(
#                 log, slack_client.client, data=results)
#             log.debug(f'results: {results}')
#     else:
#         modal = slater.GetSLaterSlashCommandNoResultsView(log,
#                                                           slack_client.client,
#                                                           f"No results for your query '{query_key}', please try again...")

#     message = modal.to_slack_message().data.get('blocks')
#     state = slack_client.send(
#         "", slack_event.get("channel"), "", blocks=message)

#     return {'ok': state.get('ok')}

# TODO: move these to plugin architecture - https://mathieularose.com/plugin-architecture-in-python
# @xray.capture()
# @logger.log
# def slater_contact_lookup(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
#     """ Return lookup details

#     Setup test environment
#     >>> os.environ['STACK_NAME'] = 'iobots-stack'

#     >>> testing.export_secret_arns(os.environ['STACK_NAME'])

#     >>> slater = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

#     >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

#     >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

#     >>> intention: str = "slater_contact_lookup"

#     >>> primary_key: str = "contact_name"

#     >>> resource: str = "contact lookup"

#     >>> data = slater.GetContacts(limit=1)

#     >>> len(data) == 1
#     True

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_contact_lookup(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(), intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid_dev.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_contact_lookup(message.get('event'),
#     ...                                      log, boto_session, agent,
#     ...                                      testing.slack_bearer_token(),
#     ...                                      intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}
#     """
#     statement: SlackStatement = SlackStatement(slack_event.get('text'))
#     words: list = statement.get('words')
#     if len(words) >= 3:
#         query_key: str = words[2]
#         # annotate('query_key', query_key)

#     http_session = requests.Session()
#     slater = SLater(log, session, http_session)
#     results = slater.GetContactsByEmailPrefix(query_key, limit=5)

#     modal: ModalView = None
#     found: int = len(results)
#     if found > 0:
#         if found == 1:
#             modal = slater.GetSLaterContactDetailView(
#                 log, slack_client.client, data=results[0])
#             log.debug(f'result: {results[0]}')
#         else:
#             modal = slater.GetSLaterContactsListView(
#                 log, slack_client.client, data=results)
#             log.debug(f'results: {results}')
#     else:
#         modal = slater.GetSLaterSlashCommandNoResultsView(log,
#                                                           slack_client.client,
#                                                           f"No results for your query '{query_key}', please try again...")

#     message = modal.to_slack_message().data.get('blocks')
#     state = slack_client.send(
#         "", slack_event.get("channel"), "", blocks=message)

#     return {'ok': state.get('ok')}

# TODO: move these to plugin architecture - https://mathieularose.com/plugin-architecture-in-python
# @xray.capture()
# @logger.log
# def slater_contact_type_lookup(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
#     """ Return lookup details

#     Setup test environment
#     >>> os.environ['STACK_NAME'] = 'iobots-stack'

#     >>> testing.export_secret_arns(os.environ['STACK_NAME'])

#     >>> slater = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

#     >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

#     >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

#     >>> intention: str = "slater_contact_type_lookup"

#     >>> primary_key: str = "contact_type"

#     >>> resource: str = "contacttype lookup"

#     >>> data = slater.GetContactTypes(limit=1)

#     >>> len(data) == 1
#     True

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_contact_type_lookup(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(), intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid_dev.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_contact_type_lookup(message.get('event'),
#     ...                                      log, boto_session, agent,
#     ...                                      testing.slack_bearer_token(),
#     ...                                      intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}
#     """
#     statement: SlackStatement = SlackStatement(slack_event.get('text'))
#     words: list = statement.get('words')
#     if len(words) >= 3:
#         query_key: str = words[2]
#         # annotate('query_key', query_key)

#     http_session = requests.Session()
#     slater = SLater(log, session, http_session)
#     results = slater.GetContactTypesByContactTypePrefix(query_key, limit=5)

#     modal: ModalView = None
#     found: int = len(results)
#     if found > 0:
#         if found == 1:
#             modal = slater.GetSLaterContactTypeDetailView(
#                 log, slack_client.client, data=results[0])
#             log.debug(f'result: {results[0]}')
#         else:
#             modal = slater.GetSLaterContactTypesListView(
#                 log, slack_client.client, data=results)
#             log.debug(f'results: {results}')
#     else:
#         modal = slater.GetSLaterSlashCommandNoResultsView(log,
#                                                           slack_client.client,
#                                                           f"No results for your query '{query_key}', please try again...")

#     message = modal.to_slack_message().data.get('blocks')
#     state = slack_client.send(
#         "", slack_event.get("channel"), "", blocks=message)

#     return {'ok': state.get('ok')}

# TODO: move these to plugin architecture - https://mathieularose.com/plugin-architecture-in-python
# @xray.capture()
# @logger.log
# def slater_lob_lookup(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
#     """ Return lob lookup details

#     Setup test environment
#     >>> os.environ['STACK_NAME'] = 'iobots-stack'

#     >>> testing.export_secret_arns(os.environ['STACK_NAME'])

#     >>> slater = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

#     >>> lobs = slater.GetLOBs(limit=1)

#     >>> len(lobs) == 1
#     True

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'lob lookup {lobs[0].get("governance_lob_name")}'
#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'lob lookup {lobs[0].get("governance_lob_name")}'

#     >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

#     >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

#     >>> response = slater_lob_lookup(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(),'slater_lob_lookup')

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid_dev.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'lob lookup {lobs[0].get("lob_id")}'
#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'lob lookup {lobs[0].get("lob_id")}'

#     >>> response = slater_lob_lookup(message.get('event'),
#     ...                                      log, boto_session, agent,
#     ...                                      testing.slack_bearer_token(),
#     ...                                      'slater_lob_lookup')

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}
#     """
#     statement: SlackStatement = SlackStatement(slack_event.get('text'))
#     words: list = statement.get('words')
#     if len(words) >= 3:
#         query_key: str = words[2]

#     http_session = requests.Session()
#     slater = SLater(log, session, http_session)
#     results = slater.GetLOBsByNamePrefix(query_key, limit=5)

#     modal: ModalView = None
#     found: int = len(results)
#     if found > 0:
#         if found == 1:
#             modal = slater.GetSLaterLOBDetailView(
#                 log, slack_client.client, data=results[0])
#             log.debug(f'result: {results[0]}')
#         else:
#             modal = slater.GetSLaterLOBsListView(
#                 log, slack_client.client, data=results)
#             log.debug(f'results: {results}')
#     else:
#         modal = slater.GetSLaterSlashCommandNoResultsView(log,
#                                                           slack_client.client,
#                                                           f"No results for your query '{query_key}', please try again...")

#     message = modal.to_slack_message().data.get('blocks')
#     state = slack_client.send(
#         "", slack_event.get("channel"), "", blocks=message)

#     return {'ok': state.get('ok')}

# TODO: move these to plugin architecture - https://mathieularose.com/plugin-architecture-in-python
# @xray.capture()
# @logger.log
# def slater_master_payer_lookup(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
#     """ Return lookup details

#     Setup test environment
#     >>> os.environ['STACK_NAME'] = 'iobots-stack'

#     >>> testing.export_secret_arns(os.environ['STACK_NAME'])

#     >>> slater = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

#     >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

#     >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

#     >>> intention: str = "slater_master_payer_lookup"

#     >>> primary_key: str = "master_payer_name"

#     >>> resource: str = "masterpayer lookup"

#     >>> data = slater.GetMasterPayers(limit=1)

#     >>> len(data) == 1
#     True

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_master_payer_lookup(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(), intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid_dev.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_master_payer_lookup(message.get('event'),
#     ...                                      log, boto_session, agent,
#     ...                                      testing.slack_bearer_token(),
#     ...                                      intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}
#     """
#     statement: SlackStatement = SlackStatement(slack_event.get('text'))
#     words: list = statement.get('words')
#     if len(words) >= 3:
#         query_key: str = words[2]

#     http_session = requests.Session()
#     slater = SLater(log, session, http_session)
#     results = slater.GetMasterPayersByNamePrefix(query_key, limit=5)

#     modal: ModalView = None
#     found: int = len(results)
#     if found > 0:
#         if found == 1:
#             modal = slater.GetSLaterMasterPayerDetailView(
#                 log, slack_client.client, data=results[0])
#             log.debug(f'result: {results[0]}')
#         else:
#             modal = slater.GetSLaterMasterPayersListView(
#                 log, slack_client.client, data=results)
#             log.debug(f'results: {results}')
#     else:
#         modal = slater.GetSLaterSlashCommandNoResultsView(log,
#                                                           slack_client.client,
#                                                           f"No results for your query '{query_key}', please try again...")

#     message = modal.to_slack_message().data.get('blocks')
#     state = slack_client.send(
#         "", slack_event.get("channel"), "", blocks=message)

#     return {'ok': state.get('ok')}


# TODO: move these to plugin architecture - https://mathieularose.com/plugin-architecture-in-python
# @xray.capture()
# @logger.log
# def slater_tower_lookup(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
#     """ Return lookup details

#     Setup test environment
#     >>> os.environ['STACK_NAME'] = 'iobots-stack'

#     >>> testing.export_secret_arns(os.environ['STACK_NAME'])

#     >>> slater = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

#     >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

#     >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

#     >>> intention: str = "slater_tower_lookup"

#     >>> primary_key: str = "tower_name"

#     >>> resource: str = "tower lookup"

#     >>> data = slater.GetTowers(limit=1)

#     >>> len(data) == 1
#     True

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_tower_lookup(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(), intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid_dev.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_tower_lookup(message.get('event'),
#     ...                                      log, boto_session, agent,
#     ...                                      testing.slack_bearer_token(),
#     ...                                      intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}
#     """
#     statement: SlackStatement = SlackStatement(slack_event.get('text'))
#     words: list = statement.get('words')
#     if len(words) >= 3:
#         query_key: str = words[2]

#     http_session = requests.Session()
#     slater = SLater(log, session, http_session)
#     results = slater.GetTowersByNamePrefix(query_key, limit=5)

#     modal: ModalView = None
#     found: int = len(results)
#     if found > 0:
#         if found == 1:
#             modal = slater.GetSLaterTowerDetailView(
#                 log, slack_client.client, data=results[0])
#             log.debug(f'result: {results[0]}')
#         else:
#             modal = slater.GetSLaterTowersListView(
#                 log, slack_client.client, data=results)
#             log.debug(f'results: {results}')
#     else:
#         modal = slater.GetSLaterSlashCommandNoResultsView(log,
#                                                           slack_client.client,
#                                                           f"No results for your query '{query_key}', please try again...")

#     message = modal.to_slack_message().data.get('blocks')
#     state = slack_client.send(
#         "", slack_event.get("channel"), "", blocks=message)

#     return {'ok': state.get('ok')}


# TODO: move these to plugin architecture - https://mathieularose.com/plugin-architecture-in-python
# @xray.capture()
# @logger.log
# def slater_root_email_lookup(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
#     """ Return lookup details

#     Setup test environment
#     >>> os.environ['STACK_NAME'] = 'iobots-stack'

#     >>> testing.export_secret_arns(os.environ['STACK_NAME'])

#     >>> slater = SLater(log,boto_session,http_session,config_file='environments/unit.ini')

#     >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

#     >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

#     >>> intention: str = "slater_root_email_lookup"

#     >>> primary_key: str = "cloud_account_root_email"

#     >>> resource: str = "rootemail lookup"

#     >>> data = slater.GetAccounts(limit=1)

#     >>> len(data) == 1
#     True

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_root_email_lookup(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(), intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}

#     >>> event_file = open('events/iobot_incoming_router_slater_application_lookup_one_tappid_dev.json', 'r')

#     >>> event = json.loads(event_file.read())

#     >>> message = get_message(event, log, boto_session)

#     >>> message['event']['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> message['event']['blocks'][0]['elements'][0]['elements'][0]['text'] = f'{resource} {data[0].get(primary_key)}'

#     >>> response = slater_root_email_lookup(message.get('event'),
#     ...                                      log, boto_session, agent,
#     ...                                      testing.slack_bearer_token(),
#     ...                                      intention)

#     Validate we have dispatched OK
#     >>> response
#     {'ok': True}
#     """
#     statement: SlackStatement = SlackStatement(slack_event.get('text'))
#     words: list = statement.get('words')
#     if len(words) >= 3:
#         query_key: str = words[2]
#         # annotate('query_key', query_key)

#     http_session = requests.Session()
#     slater = SLater(log, session, http_session)
#     results = slater.GetAccountsByRootEmailPrefix(query_key, limit=5)

#     modal: ModalView = None
#     found: int = len(results)
#     if found > 0:
#         if found == 1:
#             modal = slater.GetSLaterAccountDetailView(
#                 log, slack_client.client, data=results[0])
#             log.debug(f'result: {results[0]}')
#         else:
#             modal = slater.GetSLaterAccountsListView(
#                 log, slack_client.client, data=results)
#             log.debug(f'results: {results}')
#     else:
#         modal = slater.GetSLaterSlashCommandNoResultsView(log,
#                                                           slack_client.client,
#                                                           f"No results for your query '{query_key}', please try again...")

#     message = modal.to_slack_message().data.get('blocks')
#     state = slack_client.send(
#         "", slack_event.get("channel"), "", blocks=message)

#     return {'ok': state.get('ok')}


@xray.capture()
@logger.log
def deprecated_handler_message(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
    """Notify a user that a feature is deprecated

    Setup test environment
    >>> os.environ['STACK_NAME'] = 'iobots-stack'

    >>> testing.export_secret_arns(os.environ['STACK_NAME'])

    >>> event_file = open('events/iobot_incoming_router_deprecated_event.json', 'r')

    >>> event = json.loads(event_file.read())

    >>> message = get_message(event, log, boto_session)

    >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

    >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

    >>> response = deprecated_handler_message(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(),'export_qualys_agent_report')

    Validate we have dispatched OK
    >>> response
    {'ok': True}

    >>> testing.unexport_secret_arns()

    >>> del os.environ['STACK_NAME']
    """
    statement: SlackStatement = SlackStatement(slack_event.get('text'))
    # annotate('raw_statement', statement.get('raw'))
    # annotate('mentionless', statement.get('mentionless'))
    words: list = statement.get('words')
    # annotate('number_of_words', str(len(words)))
    # annotate('words', ','.join(words))
    log.debug(f'words: {",".join(words)}')
    # TODO: filter hellp

    help_option = Utterances(event_type=intention_event_type, get_help=True)
    deprecated_by: str = UTTERANCE_HANDLERS[intention_event_type].get(
        'deprecation').get('deprecated_by')
    view: list = []

    log.debug(f'help_option: {help_option}')
    log.debug(f'type help_option: {type(help_option)}')

    utterances: str = ""
    for utterance in help_option.get('utterances'):
        utterances += f"* _{utterance}_\n"

    view.append({
        "type": "image",
        "title": {
            "type": "plain_text",
            "text": "Uh-oh! Feature no longer supported.",
            "emoji": True
        },
        "image_url": "https://downloads.pearsoncloudmanagement.com/slater/dev/static/images/sad_panda_error_small.png",
        "alt_text": "Uh-oh! Feature no longer supported."
    })
    view.append({
        'type': 'section',
        'text': {
            'type': 'mrkdwn',
            'text': f'\"*_{statement.get("raw")}_*\" is a feature of `{intention_event_type}`. \n\n*Unfortunately, it has been deprecated and replaced by `{deprecated_by}`.* \n\n Please know this decision was not taken lightly and we strive to maintain backward compatibility when possible.\n\nInstead, you can ask one of these phrases:\n{utterances} \nDescription: {help_option.get("description")}'
        }
    })

    view.append({
        "type": "divider"
    })

    view.append({
        "type": "context",
        "elements": [{
            "type": "mrkdwn",
            "text": "*SLater* all rights reserved <https://pearson.com|:pearson: Pearson>"
        }]
    })
    state = slack_client.send("", slack_event.get("channel"), "", blocks=view)
    log.debug(f"state: {state}")
    return {'ok': state.get('ok')}


@xray.capture()
@logger.log
def general_utterances_help(slack_event, log, session, slack_client, slack_token, intention_event_type, send: bool = True, return_blocks: bool = False) -> Dict:
    """Fetch qualys report data from S3

    Setup test environment
    >>> os.environ['STACK_NAME'] = 'iobots-stack'

    >>> testing.export_secret_arns(os.environ['STACK_NAME'])

    >>> event_file = open('events/iobot_incoming_router_help_please_event.json', 'r')

    >>> event = json.loads(event_file.read())

    >>> message = get_message(event, log, boto_session)

    >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())

    >>> agent = Agent(log,boto_session,slack_client,config_file='environments/unit.ini', entitlement_level=Agent.entitlement_levels().UNRESTRICTED)

    >>> response = general_utterances_help(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(),'general_utterances_help')

    Validate we have dispatched OK
    >>> response
    {'ok': True}

    >>> event_file = open('events/iobot_incoming_router_help_please_all_event.json', 'r')

    >>> event = json.loads(event_file.read())

    >>> message = get_message(event, log, boto_session)

    >>> response = general_utterances_help(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(),'general_utterances_help')

    Validate we have dispatched OK
    >>> response
    {'ok': True}

    >>> event_file = open('events/iobot_incoming_router_help_please_qualys_keyword_event.json', 'r')

    >>> event = json.loads(event_file.read())

    >>> message = get_message(event, log, boto_session)

    >>> response = general_utterances_help(message.get('event'), log, boto_session, agent, testing.slack_bearer_token(),'general_utterances_help')

    Validate we have dispatched OK
    >>> response
    {'ok': True}

    >>> testing.unexport_secret_arns()

    >>> del os.environ['STACK_NAME']
    """

    statement: SlackStatement = SlackStatement(slack_event.get('text'))
    # annotate('raw_statement', statement.get('raw'))
    # annotate('mentionless', statement.get('mentionless'))
    words: list = statement.get('words')
    # annotate('number_of_words', str(len(words)))
    # annotate('words', ','.join(words))
    log.debug(f"number_of_words: {len(words)}, words: {words}")
    # TODO: filter hellp
    help_options = Utterances(get_help=True)
    log.debug(f'help_options: {help_options}')
    log.debug(f'type help_options: {type(help_options)}')
    config = slack_client.config

    view: list = []
    if statement.get('mentionless') == 'help please' or statement.get('mentionless') == 'help please all':

        header: str = 'Filter by keywords with "help please `<key words>`"'
        if 'all' not in words:
            header += ' or see all feature details with "help please all".'

        view = [
            {
                'type': 'section',
                'text': {
                    'type': 'mrkdwn',
                    'text': f'{header}\n'
                }
            },
            {
                "type": "context",
                "elements": [{
                    "type": "mrkdwn",
                    "text": "E.g. help please qualys"
                }]
            },
            {
                'type': 'divider'
            }
        ]

        for option in help_options:
            log.debug(f'option: {option}')
            help_option = help_options[option]

            utterances: str = ""
            if help_option.get('deprecation').get('is_deprecated'):
                deprecated_by = help_option.get(
                    'deprecation').get('deprecated_by')
                for utterance in help_options[help_option.get("deprecation").get("deprecated_by")]["utterances"]:
                    utterances += f"* _{utterance}_\n"

                if not utterances:
                    utterances = "*This feature does not respond to phrases*\n"

                if 'all' in words:
                    view.append({
                        'type': 'section',
                        'text': {
                            'type': 'mrkdwn',
                            'text': f'*Feature*: `{option}` \n\n*Note*: This feature has been deprecated by: `{deprecated_by}`\n\nPossible Phrases:\n{utterances} \nDescription: {help_options[help_option.get("deprecation").get("deprecated_by")]["description"]}'
                        }
                    })
                else:
                    view.append({
                        'type': 'section',
                        'text': {
                            'type': 'mrkdwn',
                            'text': f'*Feature*: `{option}` \n\n*Deprecated by*: `{deprecated_by}`\n\n{help_options[help_option.get("deprecation").get("deprecated_by")]["summary"]}'
                        }
                    })
            else:
                for utterance in help_option.get("utterances"):
                    utterances += f"* _{utterance}_\n"

                if not utterances:
                    utterances = "*This feature does not respond to phrases*\n"

                if 'all' in words:
                    view.append({
                        'type': 'section',
                        'text': {
                            'type': 'mrkdwn',
                            'text': f'*Feature*: `{option}` \n\nPossible Phrases:\n{utterances} \nDescription: {help_option.get("description")}'
                        }
                    })
                else:
                    view.append({
                        'type': 'section',
                        'text': {
                            'type': 'mrkdwn',
                            'text': f'*Feature*: `{option}` \n\n{help_option.get("summary")}'
                        }
                    })
            view.append({
                "type": "divider"
            })

    else:
        try:
            del words[:2]
        except Exception as err:
            log.warning(f'trapped error {err}')

        log.debug(f'revised words: {words}')
        keywords = ', '.join(words)
        view = [
            {
                'type': 'section',
                'text': {
                    'type': 'mrkdwn',
                    'text': f'Possible feature matches for keywords: *_{keywords}_*\n'
                }
            }
        ]
        for option in help_options:
            keyword_match = any(
                word in option for word in statement.get('raw').split(' '))
            if not keyword_match:
                continue

            log.debug(f'option: {option}')
            help_option = help_options[option]

            utterances: str = ""
            if help_option.get('deprecation').get('is_deprecated'):
                deprecated_by = help_option.get(
                    'deprecation').get('deprecated_by')
                for utterance in help_options[help_option.get("deprecation").get("deprecated_by")]["utterances"]:
                    utterances += f"* _{utterance}_\n"

                if not utterances:
                    utterances = "*This feature does not respond to phrases*\n"

                view.append({
                    'type': 'section',
                    'text': {
                        'type': 'mrkdwn',
                        'text': f'*Feature*: `{option}` \n\n*Note*: This feature has been deprecated by: `{deprecated_by}`\n\nPossible Phrases:\n{utterances} \nDescription: {help_options[help_option.get("deprecation").get("deprecated_by")]["description"]}'
                    }
                })
            else:
                for utterance in help_option.get("utterances"):
                    utterances += f"* _{utterance}_\n"

                if not utterances:
                    utterances = "*This feature does not respond to phrases*\n"

                block = {
                    'type': 'section',
                    'text': {
                        'type': 'mrkdwn',
                        'text': f'Feature: `{option}` \n\nPossible Phrases:\n{utterances} \nDescription: {help_option.get("description")}'
                    }
                }

                links: list = help_option.get("links")
                if links:
                    block['text']['text'] += '\n\n\n*Additional Resources:*\n'

                    for link in links:
                        ref = link.get('ref')
                        if not ref.startswith('https'):
                            ref = f"{config['HelpImages'].get('base_url')}/{ref}"

                        block['text']['text'] += f'\n* <{ref}|{link.get("title")} [{link.get("type")}]>'

                view.append(block)

            view.append({
                "type": "divider"
            })

    view.append({
        "type": "context",
        "elements": [{
            "type": "mrkdwn",
            "text": "*SLater* all rights reserved <https://pearson.com|:pearson: Pearson>"
        }]
    })

    if all([send, return_blocks]):
        raise Exception(
            "'send' and 'return_blocks' are mutually exclusive options")

    if send:
        state = slack_client.send(
            "", slack_event.get("channel"), "", blocks=view)
        log.debug(f"state: {state}")
        return {'ok': state.get('ok')}

    if return_blocks:
        return view


@xray.capture()
@logger.log
def iobot_cli(slack_event, log, session, slack_client, slack_token, intention_event_type) -> Dict:
    statement: SlackStatement = SlackStatement(slack_event.get('text'))
    # annotate('raw_statement', statement.get('raw'))
    # annotate('mentionless', statement.get('mentionless'))
    words: list = statement.get('words')
    # annotate('number_of_words', str(len(words)))
    # annotate('words', ','.join(words))
    view: list = []

    log.debug(f'passing arguments to parser: {words[1:]}')
    args, err, helpmsg = parse(words[1:])

    log.debug(f'args: {args}')
    state: Dict = {}
    if err:
        view.append({
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': f'*{err}*'
            }
        })
        view.append({
            "type": "divider"
        })
        view.append({
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': f'_{helpmsg}_'
            }
        })
        view.append({
            "type": "context",
            "elements": [{
                "type": "mrkdwn",
                "text": "*SLater* all rights reserved <https://pearson.com|:pearson: Pearson>"
            }]
        })

        state = slack_client.send(
            "", slack_event.get("channel"), "", blocks=view)
    else:
        view.append({
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': f'*{args}*'
            }
        })
        view.append({
            "type": "divider"
        })
        view.append({
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': f'_{helpmsg}_'
            }
        })
        state = slack_client.send(
            "", slack_event.get("channel"), "", blocks=view)
    log.debug(f"state: {state}")
    return {'ok': state.get('ok')}


UTTERANCE_HANDLERS: Dict = {
    'app_home_opened': {
        'handler': publish_home_page_view,
        'utterances': [],
        'deprecation': {
            'is_deprecated': False,
            'deprecated_by': None,
            'return_deprecated_handler': False
        },
        'summary': 'Auto-triggers when a user clicks on the home tab for this app',
        'description': 'Auto-triggers when a user clicks on the home tab for this app',
        'links': []
    },
    'deprecated_handler_message': {
        'handler': deprecated_handler_message,
        'utterances': [],
        'deprecation': {
            'is_deprecated': False,
            'deprecated_by': None,
            'return_deprecated_handler': False
        },
        'summary': 'Auto-triggers when a user interacts with a deprecated feature',
        'description': 'Auto-triggers when a user interacts with a deprecated feature',
        'links': []
    },
    'process_archive_file_upload_request': {
        'handler': process_archive_file_upload_request,
        'utterances': ['archive please'],
        'deprecation': {
            'is_deprecated': False,
            'deprecated_by': None,
            'return_deprecated_handler': False
        },
        'summary': 'When accompanied by an attachment, will archive slack channels',
        'description': 'When accompanied by an attachment, will archive slack channels',
        'links': [
            {
                'title': 'Archive Slack Channel Help Articles',
                'type': 'article',
                'keywords': ['archive'],
                'ref': 'https://slack.com/help/articles/201563847-Archive-a-channel'
            },
            {
                'title': 'Archive Slack Channel How-To medias',
                'type': 'media',
                'keywords': ['archive'],
                'ref': 'https://www.youtube.com/watch?v=Zlc_R6efXZ0'
            },
            {
                'title': 'Archive Slack Channel README',
                'type': 'readme',
                'keywords': ['archive'],
                'ref': 'https://gitlab.com/pearson-io-bots/iobot/-/blob/master/README.md'
            },
            {
                'title': 'Announcing Bulk Channel Archiver for Slack',
                'type': 'blog',
                'keywords': ['archive'],
                'ref': 'https://neo.pearson.com/groups/service-workflow-automation/blog/2020/06/08/announcing-bulk-channel-archiver-for-slack-beta'
            }
        ]
    },
    'export_qualys_agent_report': {
        'handler': export_qualys_agent_report,
        'utterances': [],
        'deprecation': {
            'is_deprecated': True,
            'deprecated_by': 'export_qualys_agent_report_v2',
            'return_deprecated_handler': True
        },
        'summary': '',
        'description': 'This report has been deprecated',
        'links': [
            {
                'title': 'Qualys Reporting README',
                'type': 'readme',
                'keywords': ['qualys', 'agent', 'report'],
                'ref': 'https://gitlab.com/pearson-io-bots/iobot/-/blob/master/README.md'
            },
            {
                'title': 'Announcing Qualys Reporting for Slack',
                'type': 'blog',
                'keywords': ['qualys', 'agent', 'report'],
                'ref': 'https://neo.pearson.com/groups/service-workflow-automation/blog/2020/06/14/announcing-qualys-compliance-reporting-exporter-for-slack-beta'
            }
        ]
    },
    'export_qualys_agent_report_v2': {
        'handler': export_qualys_agent_report_v2,
        'utterances': ['qualys agent report please', 'qualys agent report please {t_appid}', 'qualys agent report please {t_appid} for {t_environment}', 'qualys agent report please {t_appid} for {t_environment} in {account}', 'qualys instance report please', 'qualys instance report please {t_appid}', 'qualys instance report please {t_appid} for {t_environment}', 'qualys instance report please {t_appid} for {t_environment} in {account}', 'qualys compliance data please', 'qualys compliance data please {t_appid}', 'qualys compliance data please {t_appid} for {t_environment}', 'qualys compliance data please {t_appid} for {t_environment} in {account}'],
        'deprecation': {
            'is_deprecated': False,
            'deprecated_by': None,
            'return_deprecated_handler': False
        },
        'summary': 'Will return a menu of qualys agent compliance report interactions',
        'description': 'Will return a menu of qualys agent compliance report interactions',
        'links': [
            {'title': 'PCM Vulnerability Management',
             'type': 'article',
             'keywords': ['pcm', 'qualys', 'scanning', 'vulnerability', 'agent'],
             'ref': 'https://neo.pearson.com/docs/DOC-760909'
             },
            {'title': 'Qualys Vulnerability Scanning',
             'type': 'article',
             'keywords': ['qualys', 'scanning', 'agent'],
             'ref': 'https://neo.pearson.com/docs/DOC-754973'
             }
        ]

    },
    'export_qualys_vulnerabilities_report': {
        'handler': export_qualys_vulnerabilities_report,
        'utterances': ['qualys vuln report please', 'qualys vuln report please {t_appid}', 'qualys vuln report please {t_appid} for {t_environment}', 'qualys vuln report please {t_appid} for {t_environment} in {account}', 'qualys vulnerabilities report please', 'qualys vulnerabilities report please {t_appid}', 'qualys vulnerabilities report please {t_appid} for {t_environment}', 'qualys vulnerabilities report please {t_appid} for {t_environment} in {account}'],
        'deprecation': {
            'is_deprecated': False,
            'deprecated_by': None,
            'return_deprecated_handler': False
        },
        'summary': 'Will return a menu of qualys vulnerabilities report interactions',
        'description': 'Will return a menu of qualys vulnerabilities report interactions',
        'links': [
            {'title': 'PCM Vulnerability Management',
             'type': 'article',
             'keywords': ['pcm', 'qualys', 'scanning', 'vulnerability', 'agent'],
             'ref': 'https://neo.pearson.com/docs/DOC-760909'
             },
            {'title': 'Qualys Vulnerability Scanning',
             'type': 'article',
             'keywords': ['qualys', 'scanning', 'agent'],
             'ref': 'https://neo.pearson.com/docs/DOC-754973'
             }
        ]
    },
    'export_cloudwatch_logs': {
        'handler': export_cloudwatch_logs,
        'utterances': ['cloudwatch logs for {log_stream} in {account}'],
        'deprecation': {
            'is_deprecated': False,
            'deprecated_by': None,
            'return_deprecated_handler': False
        },
        'summary': 'Will return a menu of cloudwatch interactions',
        'description': 'Will return a menu of cloudwatch interactions',
        'links': []
    },
    'execute_jenkins_job': {
        'handler': execute_jenkins_job,
        'utterances': ['execute {job_name} on {farm_name} as {credentials} with {parameters}'],
        'deprecation': {
            'is_deprecated': False,
            'deprecated_by': None,
            'return_deprecated_handler': False
        },
        'summary': 'Will execute the job and return log results',
        'description': 'Will execute the job and return log results',
        'links': []
    },
    'iobot_cli': {
        'handler': iobot_cli,
        'utterances': ['iobot-cli --help'],
        'deprecation': {
            'is_deprecated': False,
            'deprecated_by': None,
            'return_deprecated_handler': False
        },
        'summary': 'Query the vulnerability db like a boss',
        'description': 'Query the vulnerability db like a boss',
        'links': []
    },
    # 'slater_import_upload_request': {
    #     'handler': slater_import,
    #     'utterances': ['slater import please', 'slater finops import please'],
    #     'deprecation': {
    #         'is_deprecated': False,
    #         'deprecated_by': None,
    #         'return_deprecated_handler': False
    #     },
    #     'summary': 'When accompanied by an attachment, will update slater',
    #     'description': 'When accompanied by an attachment, will update slater',
    #     'links': [
    #         {'title': 'SLater Production',
    #          'type': 'api',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://api.pearsoncloudmanagement.com/slater'
    #          },
    #         {'title': 'SLater Documentation',
    #          'type': 'article',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://neo.pearson.com/docs/DOC-764463'
    #          },
    #         {'title': 'SLater Access Request - Choose the :zap: shortcut in #io-slater-support',
    #          'type': 'shortcut',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://app.slack.com/client/T03QUQY6W/C01DNGC1ZU6'
    #          }
    #     ]
    # },
    # 'slater_account_lookup': {
    #     'handler': slater_account_lookup,
    #     'utterances': ['account lookup {account_id}', 'account lookup {account_name}'],
    #     'deprecation': {
    #         'is_deprecated': False,
    #         'deprecated_by': None,
    #         'return_deprecated_handler': False
    #     },
    #     'summary': 'Will lookup account details from slater for the provided id or name',
    #     'description': 'Will lookup account details from slater for the provided id or name',
    #     'links': [
    #         {'title': 'SLater Production',
    #          'type': 'api',
    #          'keywords': ['slater', 'account', 'lookup', 'api'],
    #          'ref': 'https://api.pearsoncloudmanagement.com/slater'
    #          },
    #         {'title': 'SLater Documentation',
    #          'type': 'article',
    #          'keywords': ['slater', 'account', 'lookup', 'api'],
    #          'ref': 'https://neo.pearson.com/docs/DOC-764463'
    #          },
    #         {'title': 'SLater Access Request - Choose the :zap: shortcut in #io-slater-support',
    #          'type': 'shortcut',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://app.slack.com/client/T03QUQY6W/C01DNGC1ZU6'
    #          },
    #         {'title': 'Account lookup in a channel - demo',
    #             'type': 'gif',
    #             'keywords': ['slater', 'account', 'lookup'],
    #             'ref': 'howto/slater-account-lookup.gif'
    #          },
    #         {'title': 'Account lookup in a DM or group chat - demo',
    #             'type': 'gif',
    #             'keywords': ['slater', 'account', 'lookup'],
    #             'ref': 'howto/slater-account-lookup-utternace-group-chat.gif'
    #          }
    #     ]
    # },
    # 'slater_account_type_lookup': {
    #     'handler': slater_account_type_lookup,
    #     'utterances': ['accounttype lookup {account_type_name}'],
    #     'deprecation': {
    #         'is_deprecated': False,
    #         'deprecated_by': None,
    #         'return_deprecated_handler': False
    #     },
    #     'summary': 'Will lookup account type details from slater for the provided id',
    #     'description': 'Will lookup account type details from slater for the provided id',
    #     'links': [
    #         {'title': 'SLater Production',
    #          'type': 'api',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://api.pearsoncloudmanagement.com/slater'
    #          },
    #         {'title': 'SLater Documentation',
    #          'type': 'article',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://neo.pearson.com/docs/DOC-764463'
    #          },
    #         {'title': 'SLater Access Request - Choose the :zap: shortcut in #io-slater-support',
    #          'type': 'shortcut',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://app.slack.com/client/T03QUQY6W/C01DNGC1ZU6'
    #          }
    #     ]
    # },
    # 'slater_application_lookup': {
    #     'handler': slater_application_lookup,
    #     'utterances': ['application lookup {t_appid}', 'application lookup {name}'],
    #     'deprecation': {
    #         'is_deprecated': False,
    #         'deprecated_by': None,
    #         'return_deprecated_handler': False
    #     },
    #     'summary': 'Will lookup application details from slater for the provided t_appid or name',
    #     'description': 'Will lookup application details from slater for the provided t_appid or name',
    #     'links': [
    #         {'title': 'SLater Production',
    #          'type': 'api',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://api.pearsoncloudmanagement.com/slater'
    #          },
    #         {'title': 'SLater Documentation',
    #          'type': 'article',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://neo.pearson.com/docs/DOC-764463'
    #          },
    #         {'title': 'SLater Access Request - Choose the :zap: shortcut in #io-slater-support',
    #          'type': 'shortcut',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://app.slack.com/client/T03QUQY6W/C01DNGC1ZU6'
    #          },
    #         {'title': 'Account lookup in a channel - demo',
    #             'type': 'gif',
    #             'keywords': ['slater', 'application', 'lookup'],
    #             'ref': 'howto/slater-account-lookup.gif'
    #          },
    #         {'title': 'Application lookup in a DM or group chat - demo',
    #             'type': 'gif',
    #             'keywords': ['slater', 'application', 'lookup'],
    #             'ref': 'howto/slater-application-lookup-utternace-group-chat.gif'
    #          }
    #     ]
    # },
    # 'slater_application_contact_lookup': {
    #     'handler': slater_application_contact_lookup,
    #     'utterances': ['applicationcontact lookup {application_contact}'],
    #     'deprecation': {
    #         'is_deprecated': False,
    #         'deprecated_by': None,
    #         'return_deprecated_handler': False
    #     },
    #     'summary': 'Will lookup application contact details from slater for the provided id',
    #     'description': 'Will lookup application contact details from slater for the provided id',
    #     'links': [
    #         {'title': 'SLater Production',
    #          'type': 'api',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://api.pearsoncloudmanagement.com/slater'
    #          },
    #         {'title': 'SLater Documentation',
    #          'type': 'article',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://neo.pearson.com/docs/DOC-764463'
    #          },
    #         {'title': 'SLater Access Request - Choose the :zap: shortcut in #io-slater-support',
    #          'type': 'shortcut',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://app.slack.com/client/T03QUQY6W/C01DNGC1ZU6'
    #          }
    #     ]
    # },
    # 'slater_business_unit_lookup': {
    #     'handler': slater_business_unit_lookup,
    #     'utterances': ['businessunit lookup {business_unit}'],
    #     'deprecation': {
    #         'is_deprecated': False,
    #         'deprecated_by': None,
    #         'return_deprecated_handler': False
    #     },
    #     'summary': 'Will lookup business unit details from slater for the provided id',
    #     'description': 'Will lookup business unit details from slater for the provided id',
    #     'links': [
    #         {'title': 'SLater Production',
    #          'type': 'api',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://api.pearsoncloudmanagement.com/slater'
    #          },
    #         {'title': 'SLater Documentation',
    #          'type': 'article',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://neo.pearson.com/docs/DOC-764463'
    #          },
    #         {'title': 'SLater Access Request - Choose the :zap: shortcut in #io-slater-support',
    #          'type': 'shortcut',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://app.slack.com/client/T03QUQY6W/C01DNGC1ZU6'
    #          }
    #     ]
    # },
    # 'slater_contact_lookup': {
    #     'handler': slater_contact_lookup,
    #     'utterances': ['contact lookup {contact_email}'],
    #     'deprecation': {
    #         'is_deprecated': False,
    #         'deprecated_by': None,
    #         'return_deprecated_handler': False
    #     },
    #     'summary': 'Will lookup contact details from slater for the provided email address',
    #     'description': 'Will lookup contact details from slater for the provided email address',
    #     'links': [
    #         {'title': 'SLater Production',
    #          'type': 'api',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://api.pearsoncloudmanagement.com/slater'
    #          },
    #         {'title': 'SLater Documentation',
    #          'type': 'article',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://neo.pearson.com/docs/DOC-764463'
    #          },
    #         {'title': 'SLater Access Request - Choose the :zap: shortcut in #io-slater-support',
    #          'type': 'shortcut',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://app.slack.com/client/T03QUQY6W/C01DNGC1ZU6'
    #          }
    #     ]
    # },
    # 'slater_contact_type_lookup': {
    #     'handler': slater_contact_type_lookup,
    #     'utterances': ['contacttype lookup {contact_type}'],
    #     'deprecation': {
    #         'is_deprecated': False,
    #         'deprecated_by': None,
    #         'return_deprecated_handler': False
    #     },
    #     'summary': 'Will lookup contact type details from slater for the provided id',
    #     'description': 'Will lookup contact type details from slater for the provided id',
    #     'links': [
    #         {'title': 'SLater Production',
    #          'type': 'api',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://api.pearsoncloudmanagement.com/slater'
    #          },
    #         {'title': 'SLater Documentation',
    #          'type': 'article',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://neo.pearson.com/docs/DOC-764463'
    #          },
    #         {'title': 'SLater Access Request - Choose the :zap: shortcut in #io-slater-support',
    #          'type': 'shortcut',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://app.slack.com/client/T03QUQY6W/C01DNGC1ZU6'
    #          }
    #     ]
    # },
    # 'slater_lob_lookup': {
    #     'handler': slater_lob_lookup,
    #     'utterances': ['lob lookup {governance_lob_name}'],
    #     'deprecation': {
    #         'is_deprecated': False,
    #         'deprecated_by': None,
    #         'return_deprecated_handler': False
    #     },
    #     'summary': 'Will lookup line of business (LOB) details from slater for the provided id',
    #     'description': 'Will lookup line of business (LOB) details from slater for the provided id',
    #     'links': [
    #         {'title': 'SLater Production',
    #          'type': 'api',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://api.pearsoncloudmanagement.com/slater'
    #          },
    #         {'title': 'SLater Documentation',
    #          'type': 'article',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://neo.pearson.com/docs/DOC-764463'
    #          },
    #         {'title': 'SLater Access Request - Choose the :zap: shortcut in #io-slater-support',
    #          'type': 'shortcut',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://app.slack.com/client/T03QUQY6W/C01DNGC1ZU6'
    #          }
    #     ]
    # },
    # 'slater_master_payer_lookup': {
    #     'handler': slater_master_payer_lookup,
    #     'utterances': ['masterpayer lookup {master_payer_name}'],
    #     'deprecation': {
    #         'is_deprecated': False,
    #         'deprecated_by': None,
    #         'return_deprecated_handler': False
    #     },
    #     'summary': 'Will lookup master payer details from slater for the provided id',
    #     'description': 'Will lookup master payer details from slater for the provided id',
    #     'links': [
    #         {'title': 'SLater Production',
    #          'type': 'api',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://api.pearsoncloudmanagement.com/slater'
    #          },
    #         {'title': 'SLater Documentation',
    #          'type': 'article',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://neo.pearson.com/docs/DOC-764463'
    #          },
    #         {'title': 'SLater Access Request - Choose the :zap: shortcut in #io-slater-support',
    #          'type': 'shortcut',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://app.slack.com/client/T03QUQY6W/C01DNGC1ZU6'
    #          }
    #     ]
    # },
    # 'slater_tower_lookup': {
    #     'handler': slater_tower_lookup,
    #     'utterances': ['tower lookup {tower_name}'],
    #     'deprecation': {
    #         'is_deprecated': False,
    #         'deprecated_by': None,
    #         'return_deprecated_handler': False
    #     },
    #     'summary': 'Will lookup tower details from slater for the provided id',
    #     'description': 'Will lookup tower details from slater for the provided id',
    #     'links': [
    #         {'title': 'SLater Production',
    #          'type': 'api',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://api.pearsoncloudmanagement.com/slater'
    #          },
    #         {'title': 'SLater Documentation',
    #          'type': 'article',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://neo.pearson.com/docs/DOC-764463'
    #          },
    #         {'title': 'SLater Access Request - Choose the :zap: shortcut in #io-slater-support',
    #          'type': 'shortcut',
    #          'keywords': ['slater', 'application', 'lookup', 'api'],
    #          'ref': 'https://app.slack.com/client/T03QUQY6W/C01DNGC1ZU6'
    #          }
    #     ]
    # },
    'general_utterances_help': {
        'handler': general_utterances_help,
        'utterances': ['help please', 'help please all', 'help please {keywords}'],
        'deprecation': {
            'is_deprecated': False,
            'deprecated_by': None,
            'return_deprecated_handler': False
        },
        'summary': 'This help menu...',
        'description': 'This help menu...',
        'links': []
    }
}


@xray.capture()
@logger.log
def parse(argv):
    parser = argparse.ArgumentParser(
        add_help=True,
        prog="iobot-cli",
        description="NLP CLI",
        epilog="""More info on this project:

        https://gitlab.com/pearson-io-bots/iobot

        Raise questions @ john.torres@pearson.com
        """)

    subparsers = parser.add_subparsers(help='sub-commands help')
    parser_qualys = subparsers.add_parser('qualys', help='qualys cli help')
    parser_qualys.add_argument('-e', '--environment', dest='environment',
                               metavar="prd", type=str, required=False, help="particular environment")
    parser_qualys.add_argument('-i', '--account-id', dest='id', metavar='0123456789012',
                               type=str, required=False, help='id of cloud account')
    parser_qualys.add_argument('-t', '--tapp-id', dest='t_appid',
                               metavar="svc01234", type=str, required=False, help="id of application")
    parser_qualys.add_argument('-d', '--date', dest='query_date',
                               metavar=f"{date.today()}", type=datetime.date, required=False, help="particular environment")
    parser_qualys.add_argument('-q', '--query', dest='query', metavar="select * from vulnerability_management.swa_current_instances_vw where account ='0123456789'",
                               type=str, required=False, help="passthrough sql query to athena")

    helpmsgs: Dict[str, str] = {}
    parsers: Dict[str, str] = {
        'default': parser,
        'qualys': parser_qualys
    }

    for key, value in parsers.items():
        helpmsgs[key] = value.format_help()

    with redirect_stderr_to_stdout():
        args: list = []

        try:
            args, unknown = parser.parse_known_args(argv)
            # put_metadata("args", args)
            # put_metadata("unknown", unknown)
            if unknown:
                err = f"unrecognized arguments: {unknown}"
                # annotate("err", err)
                return args, err, helpmsgs
            else:
                return args, None, helpmsgs
        except SystemExit as e:
            if int(f'{e}') > 0:
                # annotate("err", e)
                return args, e, helpmsgs
            else:
                return args, None, helpmsgs
