#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import annotations

import json
import re

from typing import Dict
from typing import List
from typing import Tuple

from aws_xray_sdk.core import xray_recorder as xray

from slater.auth import SLaterUserEntitlementLevel
from iobot.models import IOBotList
from iobot.models import IOBotObject

PRIMARY_KEYS: dict = {
    'Account': 'cloud_account_number',
    'AccountType': 'account_type_id',
    'Application': 'application_id',
    'ApplicationContact': 'application_contact_id',
    'BusinessUnit': 'business_unit_id',
    'Contact': 'contact_id',
    'ContactType': 'contact_type_id',
    'LOB': 'lob_id',
    'MasterPayer': 'master_payer_id',
    'Towers': 'tower_id'
}

@xray.capture()
class SLaterObject(IOBotObject):
    def __init__(self, slater_metadata: dict = None, *args, **kwargs) -> None:
        self.data = {}
        super().__init__(*args, **kwargs)
        self.slack_metadata = {
            'filtered_fields': {},
        }
        self.slater_metadata = slater_metadata if slater_metadata is not None else {}
        self._required_fields = None
        self.required_fields = self.get_required_fields()

         # unfurl kwargs into state
        for key, value in kwargs.items():
            self.__setitem__(key, value)

    def __delitem__(self, key):
        return self.data.pop(key)

    def __getitem__(self, key):
        return self.data.get(key)

    def __setitem__(self, key, value):
        if key in self:
            del self[key]
        self.data[key] = value

    @xray.capture()
    def __to_dict__(self):
        return self.data.__dict__()

    @xray.capture()
    def get(self, key, default=None):
        return self.data.get(key, default)

    @xray.capture()
    def get_blank_form_values(self):
        values: dict = {}
        for key in self.slack_element_type_map['DEFAULT'].keys():
            values[key] = ''
        return values

    @xray.capture()
    def to_json(self):
        return json.dumps(self.data, default=lambda o: o.__dict__,
                          sort_keys=True, indent=2)

    def get_required_fields(self):
        """Check if the _required_fields attribute has been set and
        return it, else loop through the slater metadata and find all columns
        where 'nullable' = False and add the results to a list and return the
        results

        >>> metadata = {'application_basic_vw': [{'ordinal_position': 1, 'column_name': 'application_id', 'data_type': 'integer', 'character_maximum_length': None, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 2, 'column_name': 'application_name', 'data_type': 'character varying', 'character_maximum_length': 255, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 3, 'column_name': 't_appid', 'data_type': 'character varying', 'character_maximum_length': 50, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 4, 'column_name': 'application_description', 'data_type': 'character varying', 'character_maximum_length': 3100, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 5, 'column_name': 'create_date', 'data_type': 'date', 'character_maximum_length': None, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 6, 'column_name': 'date_last_update', 'data_type': 'date', 'character_maximum_length': None, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 7, 'column_name': 'slater_update_user', 'data_type': 'character varying', 'character_maximum_length': 150, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 8, 'column_name': 'application_budget_account_string', 'data_type': 'character varying', 'character_maximum_length': 255, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 9, 'column_name': 'pagerduty_url', 'data_type': 'character varying', 'character_maximum_length': 500, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 10, 'column_name': 'active', 'data_type': 'integer', 'character_maximum_length': None, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 11, 'column_name': 'lob_id', 'data_type': 'bigint', 'character_maximum_length': None, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 12, 'column_name': 'governance_lob_name', 'data_type': 'USER-DEFINED', 'character_maximum_length': None, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 13, 'column_name': 'business_unit_id', 'data_type': 'integer', 'character_maximum_length': None, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 14, 'column_name': 'business_unit_name', 'data_type': 'USER-DEFINED', 'character_maximum_length': None, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 15, 'column_name': 'tower_id', 'data_type': 'integer', 'character_maximum_length': None, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 16, 'column_name': 'tower_name', 'data_type': 'USER-DEFINED', 'character_maximum_length': None, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 17, 'column_name': 'tower_contact_id', 'data_type': 'integer', 'character_maximum_length': None, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 18, 'column_name': 'tower_contact_name', 'data_type': 'character varying', 'character_maximum_length': 255, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 19, 'column_name': 'tower_contact_email', 'data_type': 'character varying', 'character_maximum_length': 255, 'is_nullable': 'YES', 'column_default': None}, {'ordinal_position': 20, 'column_name': 'row_num', 'data_type': 'bigint', 'character_maximum_length': None, 'is_nullable': 'YES', 'column_default': None}]}

        >>> test_get_required_fields = SLaterObject(slater_metadata=metadata)

        >>> test_get_required_fields.required_fields
        []
        """
        if self._required_fields is not None:
            return self._required_fields
        else:
            required_fields = []
            for table in self.slater_metadata.keys():
                for column in self.slater_metadata[table]:
                    if column.get('is_nullable') != 'YES':
                        required_fields.append(column['column_name'])
            self._required_fields = required_fields
            return self._required_fields

    @xray.capture()
    def to_slater_edit_form(self,
                            regex_field_filters: List[str] = None,
                            field_names_remap: Dict[str, str] = None,
                            field_sort_order: List[str] = None,
                            slack_element_type_map: Dict[str, str] = None,
                            slack_element_options: Dict = None,
                            skip_metadata_keys: List[str] = None) -> Tuple:
        """Take filtered fields and hide the key/value pair in object metadata
        then pass data up to IOBotObject for further processing


        Parameters
        ----------

        regex_field_filters: List[str]
          Use this option to pass multiple regular expressions to supress keys

        field_names_remap: Dict[str, str]
          Use this option to rename fields for display

        fields_sort_order: List[str]
          Use this to specify the sort order of fields for data

        slack_element_type_map: Dict[str, str]
          Mapping of key/value pairs that represent slack block_ids and
          slack element type. By default all IOBotObject elements will convert
          to 'plain_text_input'.

          Currently supported input types:
              - plain_text_input: str
              - radio_buttons: List[str,str]
              - datepicker: datetime.date
              - static_select: List[str,str]
              - external_select: str for initial_title and action_id

        skip_metadata_keys: List[str]
          A list of keys that are not to be added to object metadata

        Returns
        -------
        data : Tuple
          A collection of slack markdown sections populated with request
          mutations


        Testing
        -------


        >>> data = SLaterObject(**{ 'hello':'world','foo_bar':'bar_foo_value', 'this_field_will_be_split':'some value', 'account_id':1,'contact_id':1,'tower_id':1, 'active':0, 'empty_string':'', 'updated_last':'2021-01-10', 'decommissioned':1 })  # noqa E501

        >>> json.dumps(data.to_slater_edit_form(slack_element_type_map = {'hello':'plain_text_input', 'account_id':'external_select','contact_id':'external_select', 'tower_id':'external_select', 'active':'radio_buttons', 'updated_last':'datepicker', 'decommissioned':'static_select' }, slack_element_options = {'account_id': {'options': [], 'transform':[], 'initial_title': 'Atari', 'action_id': 'slater_selected_accounts' }, 'active': { 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'contact_id': {'options': [], 'transform':[], 'initial_title': 'Atari@root.net', 'action_id': 'slater_selected_contacts' }, 'decommissioned':{ 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'tower_id': {'options': [], 'transform':[], 'initial_title': 'Dark Tower', 'action_id': 'slater_selected_towers' }}))  # noqa E501
        '[{"type": "input", "block_id": "hello", "element": {"type": "plain_text_input", "action_id": "hello", "initial_value": "world"}, "label": {"type": "plain_text", "text": "Hello", "emoji": true}}, {"type": "input", "block_id": "foo_bar", "element": {"type": "plain_text_input", "action_id": "foo_bar", "initial_value": "bar_foo_value"}, "label": {"type": "plain_text", "text": "Foo Bar", "emoji": true}}, {"type": "input", "block_id": "this_field_will_be_split", "element": {"type": "plain_text_input", "action_id": "this_field_will_be_split", "initial_value": "some value"}, "label": {"type": "plain_text", "text": "This Field Will Be Split", "emoji": true}}, {"type": "input", "block_id": "account_id", "label": {"type": "plain_text", "text": "Account Id"}, "element": {"type": "external_select", "action_id": "slater_selected_accounts", "placeholder": {"type": "plain_text", "text": "Choose account_id"}, "min_query_length": 2, "initial_option": {"text": {"type": "plain_text", "text": "Atari"}, "value": "1"}}}, {"type": "input", "block_id": "contact_id", "label": {"type": "plain_text", "text": "Contact Id"}, "element": {"type": "external_select", "action_id": "slater_selected_contacts", "placeholder": {"type": "plain_text", "text": "Choose contact_id"}, "min_query_length": 2, "initial_option": {"text": {"type": "plain_text", "text": "Atari@root.net"}, "value": "1"}}}, {"type": "input", "block_id": "tower_id", "label": {"type": "plain_text", "text": "Tower Id"}, "element": {"type": "external_select", "action_id": "slater_selected_towers", "placeholder": {"type": "plain_text", "text": "Choose tower_id"}, "min_query_length": 2, "initial_option": {"text": {"type": "plain_text", "text": "Dark Tower"}, "value": "1"}}}, {"type": "input", "block_id": "active", "element": {"type": "radio_buttons", "options": [{"text": {"type": "plain_text", "text": "Yes"}, "value": "0"}, {"text": {"type": "plain_text", "text": "No"}, "value": "1"}], "initial_option": {"text": {"type": "plain_text", "text": "Yes"}, "value": "0"}}, "label": {"type": "plain_text", "text": "Active", "emoji": true}}, {"type": "input", "block_id": "empty_string", "element": {"type": "plain_text_input", "action_id": "empty_string"}, "label": {"type": "plain_text", "text": "Empty String", "emoji": true}}, {"type": "input", "block_id": "updated_last", "element": {"type": "datepicker", "action_id": "updated_last", "initial_date": "2021-01-10", "placeholder": {"type": "plain_text", "text": "Choose a date", "emoji": true}, "confirm": {"title": {"type": "plain_text", "text": "Change Updated Last Date"}, "text": {"type": "mrkdwn", "text": "Do you really want to change the Updated Last date?"}, "confirm": {"type": "plain_text", "text": "Yes, I do!"}, "deny": {"type": "plain_text", "text": "No, I do not!"}}}, "label": {"type": "plain_text", "text": "Updated Last", "emoji": true}}, {"type": "input", "block_id": "decommissioned", "label": {"type": "plain_text", "text": "Decommissioned"}, "element": {"type": "static_select", "action_id": "decommissioned", "placeholder": {"type": "plain_text", "text": "Choose one"}, "options": [{"text": {"type": "plain_text", "text": "Yes"}, "value": "0"}, {"text": {"type": "plain_text", "text": "No"}, "value": "1"}], "initial_option": {"text": {"type": "plain_text", "text": "No"}, "value": "1"}}}]'

        >>> json.dumps(data.to_slater_edit_form(field_names_remap={'foo_bar':'bar_foo'}, slack_element_type_map = {'hello':'plain_text_input', 'account_id':'external_select','contact_id':'external_select', 'tower_id':'external_select', 'active':'radio_buttons', 'updated_last':'datepicker', 'decommissioned':'static_select' }, slack_element_options = {'account_id': {'options': [], 'transform':[], 'initial_title': 'Atari', 'action_id': 'slater_selected_accounts' }, 'active': { 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'contact_id': {'options': [], 'transform':[], 'initial_title': 'Atari@root.net', 'action_id': 'slater_selected_contacts' }, 'decommissioned':{ 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'tower_id': {'options': [], 'transform':[], 'initial_title': 'Dark Tower', 'action_id': 'slater_selected_towers' }}))  # noqa E501
        '[{"type": "input", "block_id": "hello", "element": {"type": "plain_text_input", "action_id": "hello", "initial_value": "world"}, "label": {"type": "plain_text", "text": "Hello", "emoji": true}}, {"type": "input", "block_id": "bar_foo", "element": {"type": "plain_text_input", "action_id": "bar_foo", "initial_value": "bar_foo_value"}, "label": {"type": "plain_text", "text": "Bar Foo", "emoji": true}}, {"type": "input", "block_id": "this_field_will_be_split", "element": {"type": "plain_text_input", "action_id": "this_field_will_be_split", "initial_value": "some value"}, "label": {"type": "plain_text", "text": "This Field Will Be Split", "emoji": true}}, {"type": "input", "block_id": "account_id", "label": {"type": "plain_text", "text": "Account Id"}, "element": {"type": "external_select", "action_id": "slater_selected_accounts", "placeholder": {"type": "plain_text", "text": "Choose account_id"}, "min_query_length": 2, "initial_option": {"text": {"type": "plain_text", "text": "Atari"}, "value": "1"}}}, {"type": "input", "block_id": "contact_id", "label": {"type": "plain_text", "text": "Contact Id"}, "element": {"type": "external_select", "action_id": "slater_selected_contacts", "placeholder": {"type": "plain_text", "text": "Choose contact_id"}, "min_query_length": 2, "initial_option": {"text": {"type": "plain_text", "text": "Atari@root.net"}, "value": "1"}}}, {"type": "input", "block_id": "tower_id", "label": {"type": "plain_text", "text": "Tower Id"}, "element": {"type": "external_select", "action_id": "slater_selected_towers", "placeholder": {"type": "plain_text", "text": "Choose tower_id"}, "min_query_length": 2, "initial_option": {"text": {"type": "plain_text", "text": "Dark Tower"}, "value": "1"}}}, {"type": "input", "block_id": "active", "element": {"type": "radio_buttons", "options": [{"text": {"type": "plain_text", "text": "Yes"}, "value": "0"}, {"text": {"type": "plain_text", "text": "No"}, "value": "1"}], "initial_option": {"text": {"type": "plain_text", "text": "Yes"}, "value": "0"}}, "label": {"type": "plain_text", "text": "Active", "emoji": true}}, {"type": "input", "block_id": "empty_string", "element": {"type": "plain_text_input", "action_id": "empty_string"}, "label": {"type": "plain_text", "text": "Empty String", "emoji": true}}, {"type": "input", "block_id": "updated_last", "element": {"type": "datepicker", "action_id": "updated_last", "initial_date": "2021-01-10", "placeholder": {"type": "plain_text", "text": "Choose a date", "emoji": true}, "confirm": {"title": {"type": "plain_text", "text": "Change Updated Last Date"}, "text": {"type": "mrkdwn", "text": "Do you really want to change the Updated Last date?"}, "confirm": {"type": "plain_text", "text": "Yes, I do!"}, "deny": {"type": "plain_text", "text": "No, I do not!"}}}, "label": {"type": "plain_text", "text": "Updated Last", "emoji": true}}, {"type": "input", "block_id": "decommissioned", "label": {"type": "plain_text", "text": "Decommissioned"}, "element": {"type": "static_select", "action_id": "decommissioned", "placeholder": {"type": "plain_text", "text": "Choose one"}, "options": [{"text": {"type": "plain_text", "text": "Yes"}, "value": "0"}, {"text": {"type": "plain_text", "text": "No"}, "value": "1"}], "initial_option": {"text": {"type": "plain_text", "text": "No"}, "value": "1"}}}]'

        >>> json.dumps(data.to_slater_edit_form(regex_field_filters = [r'_id$',r'hello'], slack_element_type_map = {'hello':'plain_text_input', 'account_id':'external_select','contact_id':'external_select', 'tower_id':'external_select', 'active':'radio_buttons', 'updated_last':'datepicker', 'decommissioned':'static_select' }, slack_element_options = {'account_id': {'options': [], 'transform':[], 'initial_title': 'Atari', 'action_id': 'slater_selected_accounts' }, 'active': { 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'contact_id': {'options': [], 'transform':[], 'initial_title': 'Atari@root.net', 'action_id': 'slater_selected_contacts' }, 'decommissioned':{ 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'tower_id': {'options': [], 'transform':[], 'initial_title': 'Dark Tower', 'action_id': 'slater_selected_towers' }}))  # noqa E501
        '[{"type": "input", "block_id": "foo_bar", "element": {"type": "plain_text_input", "action_id": "foo_bar", "initial_value": "bar_foo_value"}, "label": {"type": "plain_text", "text": "Foo Bar", "emoji": true}}, {"type": "input", "block_id": "this_field_will_be_split", "element": {"type": "plain_text_input", "action_id": "this_field_will_be_split", "initial_value": "some value"}, "label": {"type": "plain_text", "text": "This Field Will Be Split", "emoji": true}}, {"type": "input", "block_id": "active", "element": {"type": "radio_buttons", "options": [{"text": {"type": "plain_text", "text": "Yes"}, "value": "0"}, {"text": {"type": "plain_text", "text": "No"}, "value": "1"}], "initial_option": {"text": {"type": "plain_text", "text": "Yes"}, "value": "0"}}, "label": {"type": "plain_text", "text": "Active", "emoji": true}}, {"type": "input", "block_id": "empty_string", "element": {"type": "plain_text_input", "action_id": "empty_string"}, "label": {"type": "plain_text", "text": "Empty String", "emoji": true}}, {"type": "input", "block_id": "updated_last", "element": {"type": "datepicker", "action_id": "updated_last", "initial_date": "2021-01-10", "placeholder": {"type": "plain_text", "text": "Choose a date", "emoji": true}, "confirm": {"title": {"type": "plain_text", "text": "Change Updated Last Date"}, "text": {"type": "mrkdwn", "text": "Do you really want to change the Updated Last date?"}, "confirm": {"type": "plain_text", "text": "Yes, I do!"}, "deny": {"type": "plain_text", "text": "No, I do not!"}}}, "label": {"type": "plain_text", "text": "Updated Last", "emoji": true}}, {"type": "input", "block_id": "decommissioned", "label": {"type": "plain_text", "text": "Decommissioned"}, "element": {"type": "static_select", "action_id": "decommissioned", "placeholder": {"type": "plain_text", "text": "Choose one"}, "options": [{"text": {"type": "plain_text", "text": "Yes"}, "value": "0"}, {"text": {"type": "plain_text", "text": "No"}, "value": "1"}], "initial_option": {"text": {"type": "plain_text", "text": "No"}, "value": "1"}}}]'

        >>> json.dumps(data.slack_metadata)  # noqa E501
        '{"filtered_fields": {"hello": "world", "account_id": 1, "contact_id": 1, "tower_id": 1}}'
        """
        if not skip_metadata_keys:
            skip_metadata_keys = []

        if regex_field_filters:
            for key, value in self.data.items():
                if any(list(re.search(pattern,
                                      key) for pattern in regex_field_filters)):
                    if key not in skip_metadata_keys:
                        self.slack_metadata['filtered_fields'][key] = value

        return self.to_slack_modal_form(regex_field_filters,
                                        field_names_remap,
                                        field_sort_order,
                                        slack_element_type_map,
                                        slack_element_options)

@xray.capture()
class SLaterList(IOBotList):
    def __init__(self, objects: list, limit: int = -1,
                  next_token: int = -1) -> None:
        self.data = []
        self.limit = limit
        self.next_token = next_token

        for obj in objects:
            self.data.append(obj)

    @xray.capture()
    def to_json(self, type_name='objects', indent=2):
        """ Test that json loads into an object properly
        >>> objects = SLaterList([SLaterObject(name='Ricktacula', id='42', ref='https://multiverse/morties/42', description='who-cares')])
        >>> data = json.loads(objects.to_json())
        >>> assert(data['objects'][0]['name'] == 'Ricktacula'), f"expected name: 'Ricktacula', got {data.name}"
        """
        # TODO: rework how this encodes objects over 'plain' keys
        objects: list = []
        for item in self.data:
            if any('SLaterObject' in obj.__name__ for obj in item.__class__.__bases__):
                reloaded = json.loads(item.to_json())
                objects.append(reloaded)
            else:
                objects.append(item.data)
        return json.dumps({type_name: objects}, indent=indent)


@xray.capture()
class Account(SLaterObject):
    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:

        self.data = {}
        super().__init__(*args, **kwargs)

        self.slack_element_type_map = {}
        default = {
            'cloud_account_name': 'plain_text_input',
            'cloud_account_number': 'plain_text_input',
            'cloud_account_root_email': 'plain_text_input',
            'account_type_id': 'external_select',
            'root_email': 'plain_text_input',
            'environment_type_id': 'external_select',
            'intake_number': 'plain_text_input',
            'governance_lob_name': 'plain_text_input',
            'master_payer_ident': 'plain_text_input',
            'budget_alert_monthly_threshold': 'plain_text_input',
            'budget_account_string': 'plain_text_input',
            'primary_funding_application_name': 'plain_text_input',
            'primary_funding_application_id': 'plain_text_input',
            'primary_funding_t_appid': 'plain_text_input',
            'project_number': 'plain_text_input',
            'snow_approval_group': 'plain_text_input',
            'provisioner': 'plain_text_input',
            'decommissioned': 'radio_buttons',
            'lob_id': 'external_select',
            'tower_id': 'external_select',
            'application_id': 'external_select',
            'master_payer_id': 'external_select'
        }
        slater_writer = {
            'cloud_account_name': 'multiline_markdown_section',
            'cloud_account_number': 'multiline_markdown_section',
            'cloud_account_root_email': 'multiline_markdown_section',
            'account_type_id': 'external_select',
            'root_email': 'plain_text_input',
            'environment_type_id': 'external_select',
            'intake_number': 'plain_text_input',
            'governance_lob_name': 'plain_text_input',
            'master_payer_ident': 'plain_text_input',
            'budget_alert_monthly_threshold': 'multiline_markdown_section',
            'budget_account_string': 'multiline_markdown_section',
            'primary_funding_application_name': 'plain_text_input',
            'primary_funding_application_id': 'plain_text_input',
            'primary_funding_t_appid': 'plain_text_input',
            'project_number': 'multiline_markdown_section',
            'snow_approval_group': 'plain_text_input',
            'provisioner': 'plain_text_input',
            'decommissioned': 'radio_buttons',
            'lob_id': 'external_select',
            'tower_id': 'external_select',
            'application_id': 'external_select',
            'master_payer_id': 'external_select'
        }
        self.slack_element_type_map['DEFAULT'] = default
        self.slack_element_type_map['SLATER_ADMIN'] = default
        self.slack_element_type_map['SLATER_CDS_WRITER'] = default
        self.slack_element_type_map['SLATER_FINOPS_WRITER'] = default
        self.slack_element_type_map['SLATER_WRITER'] = slater_writer

    @xray.capture()
    def to_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("cloud_account_name").title()} ({self.data.get("cloud_account_number")})',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("cloud_account_number")}'
        }

    @xray.capture()
    def to_human_readable(self):
        return f"""
          Account {self.get('cloud_account_number')} is *{self.get('cloud_account_name')}* and owned by {self.get('cloud_account_root_email')}.\n
          If you want a more detailed answer, ask me to _describe account _{self.get('cloud_account_number')}_
        """

    @xray.capture()
    def to_slack_modal_form(self,
                            regex_field_filters: List[str] = None,
                            field_names_remap: Dict[str, str] = None,
                            field_sort_order: List[str] = None,
                            slack_element_type_map: Dict[str, str] = None,
                            slack_element_options: Dict = None) -> Tuple:
        """ Convert our internal state to a slack json block

        >>> data = Account(cloud_account_name='Atari', cloud_account_id=1, cloud_account_number='0423456789', next_link='https://localhost/accounts/0423456789', cloud_account_description='japan-born', master_payer_ident=222222222222,environment_type_id=1,environment_type_name='non-production', cloud_account_root_email="john.torres@pearson.com",governance_lob_name="lob a", lob_id=1,budget_account_string="foo$1",decommissioned=0,provisioner="cinch",project_number="apifoo",snow_approval_group="slater snow",budget_alert_monthly_threshold=500, cloud_organization_id=1, cloud_organization_name="Assessments") # noqa E501

        >>> json.dumps(data.to_slack_modal_form(slack_element_options = { 'decommissioned': { 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'lob_id': {'options': [], 'transform':[], 'initial_title': 'Assessments', 'action_id': 'slater_selected_lobs' }, 'tower_id': {'options': [], 'transform':[], 'initial_title': 'Dark Tower', 'action_id': 'slater_selected_towers' }})) # noqa E501
        '[{"type": "input", "block_id": "cloud_account_name", "element": {"type": "plain_text_input", "action_id": "cloud_account_name", "initial_value": "Atari"}, "label": {"type": "plain_text", "text": "Cloud Account Name", "emoji": true}}, {"type": "input", "block_id": "cloud_account_id", "element": {"type": "plain_text_input", "action_id": "cloud_account_id", "initial_value": "1"}, "label": {"type": "plain_text", "text": "Cloud Account Id", "emoji": true}}, {"type": "input", "block_id": "cloud_account_number", "element": {"type": "plain_text_input", "action_id": "cloud_account_number", "initial_value": "0423456789"}, "label": {"type": "plain_text", "text": "Cloud Account Number", "emoji": true}}, {"type": "input", "block_id": "next_link", "element": {"type": "plain_text_input", "action_id": "next_link", "initial_value": "https://localhost/accounts/0423456789"}, "label": {"type": "plain_text", "text": "Next Link", "emoji": true}}, {"type": "input", "block_id": "cloud_account_description", "element": {"type": "plain_text_input", "action_id": "cloud_account_description", "initial_value": "japan-born"}, "label": {"type": "plain_text", "text": "Cloud Account Description", "emoji": true}}, {"type": "input", "block_id": "master_payer_ident", "element": {"type": "plain_text_input", "action_id": "master_payer_ident", "initial_value": "222222222222"}, "label": {"type": "plain_text", "text": "Master Payer Ident", "emoji": true}}, {"type": "input", "block_id": "environment_type_id", "label": {"type": "plain_text", "text": "Environment Type Id"}, "element": {"type": "external_select", "action_id": "environment_type_id", "placeholder": {"type": "plain_text", "text": "Choose environment_type_id"}, "min_query_length": 2}}, {"type": "input", "block_id": "environment_type_name", "element": {"type": "plain_text_input", "action_id": "environment_type_name", "initial_value": "non-production"}, "label": {"type": "plain_text", "text": "Environment Type Name", "emoji": true}}, {"type": "input", "block_id": "cloud_account_root_email", "element": {"type": "plain_text_input", "action_id": "cloud_account_root_email", "initial_value": "john.torres@pearson.com"}, "label": {"type": "plain_text", "text": "Cloud Account Root Email", "emoji": true}}, {"type": "input", "block_id": "governance_lob_name", "element": {"type": "plain_text_input", "action_id": "governance_lob_name", "initial_value": "lob a"}, "label": {"type": "plain_text", "text": "Governance Lob Name", "emoji": true}}, {"type": "input", "block_id": "lob_id", "label": {"type": "plain_text", "text": "Lob Id"}, "element": {"type": "external_select", "action_id": "slater_selected_lobs", "placeholder": {"type": "plain_text", "text": "Choose lob_id"}, "min_query_length": 2, "initial_option": {"text": {"type": "plain_text", "text": "Assessments"}, "value": "1"}}}, {"type": "input", "block_id": "budget_account_string", "element": {"type": "plain_text_input", "action_id": "budget_account_string", "initial_value": "foo$1"}, "label": {"type": "plain_text", "text": "Budget Account String", "emoji": true}}, {"type": "input", "block_id": "decommissioned", "element": {"type": "radio_buttons", "options": [{"text": {"type": "plain_text", "text": "Yes"}, "value": "0"}, {"text": {"type": "plain_text", "text": "No"}, "value": "1"}], "initial_option": {"text": {"type": "plain_text", "text": "Yes"}, "value": "0"}}, "label": {"type": "plain_text", "text": "Decommissioned", "emoji": true}}, {"type": "input", "block_id": "provisioner", "element": {"type": "plain_text_input", "action_id": "provisioner", "initial_value": "cinch"}, "label": {"type": "plain_text", "text": "Provisioner", "emoji": true}}, {"type": "input", "block_id": "project_number", "element": {"type": "plain_text_input", "action_id": "project_number", "initial_value": "apifoo"}, "label": {"type": "plain_text", "text": "Project Number", "emoji": true}}, {"type": "input", "block_id": "snow_approval_group", "element": {"type": "plain_text_input", "action_id": "snow_approval_group", "initial_value": "slater snow"}, "label": {"type": "plain_text", "text": "Snow Approval Group", "emoji": true}}, {"type": "input", "block_id": "budget_alert_monthly_threshold", "element": {"type": "plain_text_input", "action_id": "budget_alert_monthly_threshold", "initial_value": "500"}, "label": {"type": "plain_text", "text": "Budget Alert Monthly Threshold", "emoji": true}}, {"type": "input", "block_id": "cloud_organization_id", "element": {"type": "plain_text_input", "action_id": "cloud_organization_id", "initial_value": "1"}, "label": {"type": "plain_text", "text": "Cloud Organization Id", "emoji": true}}, {"type": "input", "block_id": "cloud_organization_name", "element": {"type": "plain_text_input", "action_id": "cloud_organization_name", "initial_value": "Assessments"}, "label": {"type": "plain_text", "text": "Cloud Organization Name", "emoji": true}}]'

        >>> json.dumps(data.to_slack_modal_form(regex_field_filters = [r'application_id',r'_link$', r'update_date'], slack_element_type_map = { 'application_name': 'plain_text_input', 't_appid': 'plain_text_input', 'application_description': 'plain_text_input', 'line_of_business': 'external_select', 'create_date': 'datepicker', 'update_date': 'datepicker', 'application_budget_account_string': 'plain_text_input', 'active': 'radio_buttons', 'business_unit_id': 'external_select', 'pagerduty_url': 'plain_text_input', 'tower_id': 'external_select' }, slack_element_options = {'account_id': {'options': [], 'transform':[], 'initial_title': 'Atari', 'action_id': 'slater_selected_accounts' }, 'active': { 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'contact_id': {'options': [], 'transform':[], 'initial_title': 'Atari@root.net', 'action_id': 'slater_selected_contacts' }, 'decommissioned':{ 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'tower_id': {'options': [], 'transform':[], 'initial_title': 'Dark Tower', 'action_id': 'slater_selected_towers' }}))  # noqa E501
        '[{"type": "input", "block_id": "cloud_account_name", "element": {"type": "plain_text_input", "action_id": "cloud_account_name", "initial_value": "Atari"}, "label": {"type": "plain_text", "text": "Cloud Account Name", "emoji": true}}, {"type": "input", "block_id": "cloud_account_id", "element": {"type": "plain_text_input", "action_id": "cloud_account_id", "initial_value": "1"}, "label": {"type": "plain_text", "text": "Cloud Account Id", "emoji": true}}, {"type": "input", "block_id": "cloud_account_number", "element": {"type": "plain_text_input", "action_id": "cloud_account_number", "initial_value": "0423456789"}, "label": {"type": "plain_text", "text": "Cloud Account Number", "emoji": true}}, {"type": "input", "block_id": "cloud_account_description", "element": {"type": "plain_text_input", "action_id": "cloud_account_description", "initial_value": "japan-born"}, "label": {"type": "plain_text", "text": "Cloud Account Description", "emoji": true}}, {"type": "input", "block_id": "master_payer_ident", "element": {"type": "plain_text_input", "action_id": "master_payer_ident", "initial_value": "222222222222"}, "label": {"type": "plain_text", "text": "Master Payer Ident", "emoji": true}}, {"type": "input", "block_id": "environment_type_id", "element": {"type": "plain_text_input", "action_id": "environment_type_id", "initial_value": "1"}, "label": {"type": "plain_text", "text": "Environment Type Id", "emoji": true}}, {"type": "input", "block_id": "environment_type_name", "element": {"type": "plain_text_input", "action_id": "environment_type_name", "initial_value": "non-production"}, "label": {"type": "plain_text", "text": "Environment Type Name", "emoji": true}}, {"type": "input", "block_id": "cloud_account_root_email", "element": {"type": "plain_text_input", "action_id": "cloud_account_root_email", "initial_value": "john.torres@pearson.com"}, "label": {"type": "plain_text", "text": "Cloud Account Root Email", "emoji": true}}, {"type": "input", "block_id": "governance_lob_name", "element": {"type": "plain_text_input", "action_id": "governance_lob_name", "initial_value": "lob a"}, "label": {"type": "plain_text", "text": "Governance Lob Name", "emoji": true}}, {"type": "input", "block_id": "lob_id", "element": {"type": "plain_text_input", "action_id": "lob_id", "initial_value": "1"}, "label": {"type": "plain_text", "text": "Lob Id", "emoji": true}}, {"type": "input", "block_id": "budget_account_string", "element": {"type": "plain_text_input", "action_id": "budget_account_string", "initial_value": "foo$1"}, "label": {"type": "plain_text", "text": "Budget Account String", "emoji": true}}, {"type": "input", "block_id": "decommissioned", "element": {"type": "plain_text_input", "action_id": "decommissioned"}, "label": {"type": "plain_text", "text": "Decommissioned", "emoji": true}}, {"type": "input", "block_id": "provisioner", "element": {"type": "plain_text_input", "action_id": "provisioner", "initial_value": "cinch"}, "label": {"type": "plain_text", "text": "Provisioner", "emoji": true}}, {"type": "input", "block_id": "project_number", "element": {"type": "plain_text_input", "action_id": "project_number", "initial_value": "apifoo"}, "label": {"type": "plain_text", "text": "Project Number", "emoji": true}}, {"type": "input", "block_id": "snow_approval_group", "element": {"type": "plain_text_input", "action_id": "snow_approval_group", "initial_value": "slater snow"}, "label": {"type": "plain_text", "text": "Snow Approval Group", "emoji": true}}, {"type": "input", "block_id": "budget_alert_monthly_threshold", "element": {"type": "plain_text_input", "action_id": "budget_alert_monthly_threshold", "initial_value": "500"}, "label": {"type": "plain_text", "text": "Budget Alert Monthly Threshold", "emoji": true}}, {"type": "input", "block_id": "cloud_organization_id", "element": {"type": "plain_text_input", "action_id": "cloud_organization_id", "initial_value": "1"}, "label": {"type": "plain_text", "text": "Cloud Organization Id", "emoji": true}}, {"type": "input", "block_id": "cloud_organization_name", "element": {"type": "plain_text_input", "action_id": "cloud_organization_name", "initial_value": "Assessments"}, "label": {"type": "plain_text", "text": "Cloud Organization Name", "emoji": true}}]'

        """
        if not slack_element_type_map:
            slack_element_type_map = self.slack_element_type_map.get('DEFAULT')
        return super().to_slack_modal_form(regex_field_filters=regex_field_filters,
                                           field_names_remap=field_names_remap,
                                           field_sort_order=field_sort_order,
                                           slack_element_type_map=slack_element_type_map,
                                           slack_element_options=slack_element_options)

    def to_edit_form(self, entitlement_level: SLaterUserEntitlementLevel = None) -> Tuple:
        """ Convert our internal state to a slack json block

        >>> data = Account(cloud_account_name='Atari', cloud_account_id=1, cloud_account_number='0423456789', account_type_id=1, account_type_name='PSI Test Account Type', next_link='https://localhost/accounts/0423456789', cloud_account_description='japan-born', master_payer_ident=222222222222, master_payer_id=2, master_payer_name='PSI Payer Test', cloud_account_root_email="john.torres@pearson.com",governance_lob_name="lob a", lob_id=1,budget_account_string="foo$1",decommissioned=0,provisioner="cinch",project_number="apifoo",snow_approval_group="slater snow",budget_alert_monthly_threshold=500, cloud_organization_id=1, cloud_organization_name="Assessments", primary_funding_application_id=1, primary_funding_t_appid='PSI0001', primary_funding_applcation_name='PSI Test App', intake_number=10, tower_id=1, tower_name='PSI Test Tower', create_date='01/01/1970', date_last_update='01/01/1970', slater_update_user='PSI Test', row_num=1, ) # noqa E501

        >>> json.dumps(data.to_edit_form(SLaterUserEntitlementLevel.SLATER_WRITER.name)) # noqa E501
        '[{"type": "section", "block_id": "cloud_account_name-title", "text": {"type": "mrkdwn", "text": "*Cloud Account Name*"}}, {"type": "section", "block_id": "cloud_account_name", "text": {"type": "mrkdwn", "text": "Atari"}}, {"type": "section", "block_id": "cloud_account_number-title", "text": {"type": "mrkdwn", "text": "*Cloud Account Number*"}}, {"type": "section", "block_id": "cloud_account_number", "text": {"type": "mrkdwn", "text": "0423456789"}}, {"type": "section", "block_id": "cloud_account_root_email-title", "text": {"type": "mrkdwn", "text": "*Cloud Account Root Email*"}}, {"type": "section", "block_id": "cloud_account_root_email", "text": {"type": "mrkdwn", "text": "john.torres@pearson.com"}}, {"type": "input", "block_id": "account_type_id", "label": {"type": "plain_text", "text": "Environment"}, "element": {"type": "external_select", "action_id": "slater_selected_account_types", "placeholder": {"type": "plain_text", "text": "Choose account_type_id"}, "min_query_length": 2, "initial_option": {"text": {"type": "plain_text", "text": "PSI Test Account Type"}, "value": "1"}}}, {"type": "input", "block_id": "master_payer_id", "label": {"type": "plain_text", "text": "Master Payer"}, "element": {"type": "external_select", "action_id": "slater_selected_master_payers", "placeholder": {"type": "plain_text", "text": "Choose master_payer_id"}, "min_query_length": 2, "initial_option": {"text": {"type": "plain_text", "text": "PSI Payer Test"}, "value": "2"}}}, {"type": "section", "block_id": "budget_alert_monthly_threshold-title", "text": {"type": "mrkdwn", "text": "*Budget Alert Monthly Threshold*"}}, {"type": "section", "block_id": "budget_alert_monthly_threshold", "text": {"type": "mrkdwn", "text": "500"}}, {"type": "section", "block_id": "budget_account_string-title", "text": {"type": "mrkdwn", "text": "*Budget Account String*"}}, {"type": "section", "block_id": "budget_account_string", "text": {"type": "mrkdwn", "text": "foo$1"}}, {"type": "section", "block_id": "project_number-title", "text": {"type": "mrkdwn", "text": "*Project Number*"}}, {"type": "section", "block_id": "project_number", "text": {"type": "mrkdwn", "text": "apifoo"}}, {"type": "input", "block_id": "lob_id", "label": {"type": "plain_text", "text": "Governance Lob"}, "element": {"type": "external_select", "action_id": "slater_selected_lobs", "placeholder": {"type": "plain_text", "text": "Choose lob_id"}, "min_query_length": 2, "initial_option": {"text": {"type": "plain_text", "text": "lob a"}, "value": "1"}}}, {"type": "input", "block_id": "tower_id", "label": {"type": "plain_text", "text": "Tower"}, "element": {"type": "external_select", "action_id": "slater_selected_towers", "placeholder": {"type": "plain_text", "text": "Choose tower_id"}, "min_query_length": 2, "initial_option": {"text": {"type": "plain_text", "text": "PSI Test Tower"}, "value": "1"}}}, {"type": "input", "block_id": "intake_number", "element": {"type": "plain_text_input", "action_id": "intake_number", "initial_value": "10"}, "label": {"type": "plain_text", "text": "Intake Number", "emoji": true}}, {"type": "input", "block_id": "decommissioned", "element": {"type": "radio_buttons", "options": [{"text": {"type": "plain_text", "text": "Yes"}, "value": "0"}, {"text": {"type": "plain_text", "text": "No"}, "value": "1"}], "initial_option": {"text": {"type": "plain_text", "text": "Yes"}, "value": "0"}}, "label": {"type": "plain_text", "text": "Decommissioned", "emoji": true}}]'
        """
        # Common params can be defined here
        regex_field_filters = [
            r'cloud_account_id',
            r'_link$',
            r'update_date',
        ]
        skip_metadata_keys = [
            'date_last_update',
            'slater_update_user',
            'next_link',
            'governance_lob_name',
            'business_unit_name',
            'tower_name',
            'tower_contact_id',
            'tower_contact_name',
            'tower_contact_email',
            'row_num'
        ]
        field_sort_order = [
            'cloud_account_name',
            'cloud_account_number',
            'cloud_account_root_email',
            'account_type_id',
            'master_payer_id',
            'budget_alert_monthly_threshold',
            'budget_account_string',
            'project_number',
            'lob_id',
            'tower_id',
            'intake_number',
            'decommissioned',
        ]
        slack_element_options = {
            'account_type_id': {
                'options': [],
                'transform': [],
                'initial_title': self.get('account_type_name'),
                'action_id': 'slater_selected_account_types',
                'label': 'Environment'
            },
            'decommissioned': {
                'options': [{'Yes': '0'}, {'No': '1'}],
                'transform': [{'0': 'Yes'}, {'1': 'No'}]
            },
            'lob_id': {
                'options': [],
                'transform': [],
                'initial_title': self.get('governance_lob_name'),
                'action_id': 'slater_selected_lobs',
                'label': 'Governance Lob'
            },
            'master_payer_id': {
                'options': [],
                'transform': [],
                'initial_title': self.get('master_payer_name'),
                'action_id': 'slater_selected_master_payers',
                'label': 'Master Payer'
            },
            'tower_id': {
                'options': [],
                'transform': [],
                'initial_title': self.get('tower_name'),
                'action_id': 'slater_selected_towers',
                'label': 'Tower'
            }
        }
        field_names_remap = None
        slack_element_type_map = self.slack_element_type_map.get(
            entitlement_level,
            self.slack_element_type_map['DEFAULT']
        )

        entitlement_config = {
            'DEFAULT': {
                'regex_field_filters': regex_field_filters,
                'field_names_remap': field_names_remap,
                'field_sort_order': field_sort_order,
                'slack_element_type_map': slack_element_type_map,
                'slack_element_options': slack_element_options,
                'skip_metadata_keys': skip_metadata_keys,
            },
            'SLATER_WRITER': {
                'regex_field_filters': regex_field_filters,
                'field_names_remap': field_names_remap,
                'field_sort_order': field_sort_order,
                'slack_element_type_map': slack_element_type_map,
                'slack_element_options': slack_element_options,
                'skip_metadata_keys': skip_metadata_keys,
            },
            'SLATER_CDS_WRITER': {
                'regex_field_filters': regex_field_filters,
                'field_names_remap': field_names_remap,
                'field_sort_order': field_sort_order,
                'slack_element_type_map': slack_element_type_map,
                'slack_element_options': slack_element_options,
                'skip_metadata_keys': skip_metadata_keys,
            },
            'SLATER_FINOPS_WRITER': {
                'regex_field_filters': regex_field_filters,
                'field_names_remap': field_names_remap,
                'field_sort_order': field_sort_order,
                'slack_element_type_map': slack_element_type_map,
                'slack_element_options': slack_element_options,
                'skip_metadata_keys': skip_metadata_keys,
            },
            'SLATER_ADMIN': {
                'regex_field_filters': regex_field_filters,
                'field_names_remap': field_names_remap,
                'field_sort_order': field_sort_order,
                'slack_element_type_map': slack_element_type_map,
                'slack_element_options': slack_element_options,
                'skip_metadata_keys': skip_metadata_keys,
            }
        }

        return super().to_slater_edit_form(**entitlement_config.get(
            entitlement_level,
            entitlement_config['DEFAULT'])
        )


class Accounts(SLaterList):
    @xray.capture()
    def __init__(self, accounts: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(accounts, limit=limit, next_token=next_token)

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> accounts = Accounts([Account(cloud_account_name='Atari', cloud_account_id=1, cloud_account_number='0423456789', next_link='https://localhost/accounts/0423456789', cloud_account_description='japan-born', master_payer_ident=222222222222,environment_type_id=1,environment_type_name='non-production', cloud_account_root_email="john.torres@pearson.com",governance_lob_name="lob a", lob_id=1,budget_account_string="foo$1",decommissioned=0,provisioner="cinch",project_number="apifoo",snow_approval_group="slater snow",budget_alert_monthly_threshold=500, cloud_organization_id=1, cloud_organization_name="Assessments")])

        >>> data = json.loads(accounts.to_json())

        >>> data['accounts'][0]['cloud_account_name']
        'Atari'
        """
        return super().to_json(type_name='accounts')

    @xray.capture()
    def to_csv(self):
        """ Test that csv data loads into a pandas dataframe
        >>> accounts = Accounts([Account(cloud_account_name='Atari', cloud_account_id=1, cloud_account_number='0423456789', next_link='https://localhost/accounts/0423456789', cloud_account_description='japan-born', master_payer_ident=222222222222,environment_type_id=1,environment_type_name='non-production', cloud_account_root_email="john.torres@pearson.com",governance_lob_name="lob a", lob_id=1,budget_account_string="foo$1",decommissioned=0,provisioner="cinch",project_number="apifoo",snow_approval_group="slater snow",budget_alert_monthly_threshold=500, cloud_organization_id=1, cloud_organization_name="Assessments")])

        >>> data = accounts.to_csv().lstrip().splitlines()

        >>> len(data)
        2
        """
        return super().to_csv()


@xray.capture()
class AccountType(SLaterObject):
    """
    >>> account_type = AccountType(account_type_name='non-production', account_type_id='1', next_link='https://localhost/accounttypes?account_type_id=1', date_last_update='2020-07-22')

    >>> account_type.get('account_type_name')
    'non-production'

    """
    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:

        self.data = {}
        super().__init__(*args, **kwargs)
        self.slack_element_type_map = {}
        default = {
            'account_type_name': 'plain_text_input'
        }
        slater_writer = {
            'account_type_name': 'plain_text_input'
        }
        self.slack_element_type_map['DEFAULT'] = default
        self.slack_element_type_map['SLATER_ADMIN'] = default
        self.slack_element_type_map['SLATER_CDS_WRITER'] = default
        self.slack_element_type_map['SLATER_FINOPS_WRITER'] = default
        self.slack_element_type_map['SLATER_WRITER'] = slater_writer

    @xray.capture()
    # override default find by id
    def to_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("account_type_name").title()}',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("account_type_id")}'
        }

    @xray.capture()
    def to_slack_modal_form(self,
                            regex_field_filters: List[str] = None,
                            field_names_remap: Dict[str, str] = None,
                            field_sort_order: List[str] = None,
                            slack_element_type_map: Dict[str, str] = None,
                            slack_element_options: Dict = None) -> Tuple:
        """ Convert our internal state to a slack json block

        >>> data = AccountType(**{'account_type_name': 'non-production', 'account_type_id': '124', 'next_link': 'https://localhost/accounttypes?account_type_id=1', 'date_last_update': '1970-01-01'}) # noqa E501

        >>> json.dumps(data.to_slack_modal_form())
        '[{"type": "input", "block_id": "account_type_name", "element": {"type": "plain_text_input", "action_id": "account_type_name", "initial_value": "non-production"}, "label": {"type": "plain_text", "text": "Account Type Name", "emoji": true}}, {"type": "input", "block_id": "account_type_id", "element": {"type": "plain_text_input", "action_id": "account_type_id", "initial_value": "124"}, "label": {"type": "plain_text", "text": "Account Type Id", "emoji": true}}, {"type": "input", "block_id": "next_link", "element": {"type": "plain_text_input", "action_id": "next_link", "initial_value": "https://localhost/accounttypes?account_type_id=1"}, "label": {"type": "plain_text", "text": "Next Link", "emoji": true}}, {"type": "input", "block_id": "date_last_update", "element": {"type": "plain_text_input", "action_id": "date_last_update", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Date Last Update", "emoji": true}}]'

        >>> json.dumps(data.to_slack_modal_form(regex_field_filters = [r'_id',r'_link$', r'update_date']))  # noqa E501
        '[{"type": "input", "block_id": "account_type_name", "element": {"type": "plain_text_input", "action_id": "account_type_name", "initial_value": "non-production"}, "label": {"type": "plain_text", "text": "Account Type Name", "emoji": true}}, {"type": "input", "block_id": "date_last_update", "element": {"type": "plain_text_input", "action_id": "date_last_update", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Date Last Update", "emoji": true}}]'

        """
        if not slack_element_type_map:
            slack_element_type_map = self.slack_element_type_map.get('DEFAULT')
        return super().to_slack_modal_form(regex_field_filters=regex_field_filters,
                                           field_names_remap=field_names_remap,
                                           field_sort_order=field_sort_order,
                                           slack_element_type_map=slack_element_type_map,
                                           slack_element_options=slack_element_options)


@xray.capture()
class AccountTypes(SLaterList):
    @xray.capture()
    def __init__(self, accounttypes: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(accounttypes, limit=limit, next_token=next_token)

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> account_types = AccountTypes([AccountType(account_type_name='non-production', account_type_id='1', next_link='https://localhost/accounttypes?account_type_id=1', date_last_update='2020-07-22')])

        >>> data = json.loads(account_types.to_json())

        >>> data['account_types'][0]['account_type_name']
        'non-production'
        """
        return super().to_json(type_name='account_types')

    @xray.capture()
    def to_csv(self):
        """ Test that csv data loads into a pandas dataframe
        >>> account_types = AccountTypes([AccountType(account_type_name='non-production', account_type_id='1', next_link='https://localhost/accounttypes?account_type_id=1', date_last_update='2020-07-22')])

        >>> data = account_types.to_csv().lstrip().splitlines()

        >>> assert (len(data) == 2), f'expected two rows (header and one data record), got {len(data)} rows'
        """
        return super().to_csv()


@xray.capture()
class Application(SLaterObject):
    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:

        self.data = {}
        super().__init__(*args, **kwargs)

        self.slack_element_type_map = {}
        default = {
            'application_name': 'plain_text_input',
            't_appid': 'plain_text_input',
            'application_description': 'plain_text_input',
            'lob_id': 'external_select',
            'application_budget_account_string': 'plain_text_input',
            'active': 'radio_buttons',
            'business_unit_id': 'external_select',
            'pagerduty_url': 'plain_text_input',
            'tower_id': 'external_select'
        }
        slater_writer = {
            'application_name': 'plain_text_input',
            't_appid': 'plain_text_input',
            'application_description': 'plain_text_input',
            'lob_id': 'external_select',
            'application_budget_account_string': 'multiline_markdown_section',
            'active': 'radio_buttons',
            'business_unit_id': 'external_select',
            'pagerduty_url': 'plain_text_input',
            'tower_id': 'external_select'
        }
        self.slack_element_type_map['DEFAULT'] = default
        self.slack_element_type_map['SLATER_ADMIN'] = default
        self.slack_element_type_map['SLATER_CDS_WRITER'] = default
        self.slack_element_type_map['SLATER_FINOPS_WRITER'] = default
        self.slack_element_type_map['SLATER_WRITER'] = slater_writer

    @xray.capture()
    # override our default which uses the 'id' field
    def to_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("application_name").title()} ({self.data.get("t_appid")})',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("application_id")}'
        }

    @xray.capture()
    def to_lobs_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("lob_name").title()} ({self.data.get("lob_id")})',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("lob_id")}'
        }

    @xray.capture()
    def to_slack_modal_form(self,
                            regex_field_filters: List[str] = None,
                            field_names_remap: Dict[str, str] = None,
                            field_sort_order: List[str] = None,
                            slack_element_type_map: Dict[str, str] = None,
                            slack_element_options: Dict = None) -> Tuple:
        """ Convert our internal state to a slack json block

        >>> data = Application(**{'application_name': 'Joust', 'application_id': '6', 'next_link': 'https://localhost/applications?application_id=6', 't_appid': 'SVCD006', 'application_description': 'Ride a Ostrich', 'lob_id': '1', 'create_date': '1970-01-01', 'update_date': '1970-01-01', 'application_budget_account_string': 'budget$1'}) # noqa E501

        >>> json.dumps(data.to_slack_modal_form(slack_element_options = { 'active': { 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'lob_id': {'options': [], 'transform':[], 'initial_title': 'Assessments', 'action_id': 'slater_selected_lobs' }, 'tower_id': {'options': [], 'transform':[], 'initial_title': 'Dark Tower', 'action_id': 'slater_selected_towers' }})) # noqa E501
        '[{"type": "input", "block_id": "application_name", "element": {"type": "plain_text_input", "action_id": "application_name", "initial_value": "Joust"}, "label": {"type": "plain_text", "text": "Application Name", "emoji": true}}, {"type": "input", "block_id": "application_id", "element": {"type": "plain_text_input", "action_id": "application_id", "initial_value": "6"}, "label": {"type": "plain_text", "text": "Application Id", "emoji": true}}, {"type": "input", "block_id": "next_link", "element": {"type": "plain_text_input", "action_id": "next_link", "initial_value": "https://localhost/applications?application_id=6"}, "label": {"type": "plain_text", "text": "Next Link", "emoji": true}}, {"type": "input", "block_id": "t_appid", "element": {"type": "plain_text_input", "action_id": "t_appid", "initial_value": "SVCD006"}, "label": {"type": "plain_text", "text": "t_AppID", "emoji": true}}, {"type": "input", "block_id": "application_description", "element": {"type": "plain_text_input", "action_id": "application_description", "initial_value": "Ride a Ostrich"}, "label": {"type": "plain_text", "text": "Application Description", "emoji": true}}, {"type": "input", "block_id": "lob_id", "label": {"type": "plain_text", "text": "Lob Id"}, "element": {"type": "external_select", "action_id": "slater_selected_lobs", "placeholder": {"type": "plain_text", "text": "Choose lob_id"}, "min_query_length": 2, "initial_option": {"text": {"type": "plain_text", "text": "Assessments"}, "value": "1"}}}, {"type": "input", "block_id": "create_date", "element": {"type": "plain_text_input", "action_id": "create_date", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Create Date", "emoji": true}}, {"type": "input", "block_id": "update_date", "element": {"type": "plain_text_input", "action_id": "update_date", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Update Date", "emoji": true}}, {"type": "input", "block_id": "application_budget_account_string", "element": {"type": "plain_text_input", "action_id": "application_budget_account_string", "initial_value": "budget$1"}, "label": {"type": "plain_text", "text": "Application Budget Account String", "emoji": true}}]'

        >>> json.dumps(data.to_slack_modal_form(slack_element_type_map = { 'application_name': 'plain_text_input', 't_appid': 'plain_text_input', 'application_description': 'plain_text_input', 'line_of_business': 'external_select', 'create_date': 'datepicker', 'update_date': 'datepicker', 'application_budget_account_string': 'plain_text_input', 'active': 'radio_buttons', 'business_unit_id': 'external_select', 'pagerduty_url': 'plain_text_input', 'tower_id': 'external_select' }, field_names_remap={'lob_id':'line_of_business'}, slack_element_options = {'account_id': {'options': [], 'transform':[], 'initial_title': 'Atari', 'action_id': 'slater_selected_accounts' }, 'active': { 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'contact_id': {'options': [], 'transform':[], 'initial_title': 'Atari@root.net', 'action_id': 'slater_selected_contacts' }, 'decommissioned':{ 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'tower_id': {'options': [], 'transform':[], 'initial_title': 'Dark Tower', 'action_id': 'slater_selected_towers' }}))  # noqa E501
        '[{"type": "input", "block_id": "application_name", "element": {"type": "plain_text_input", "action_id": "application_name", "initial_value": "Joust"}, "label": {"type": "plain_text", "text": "Application Name", "emoji": true}}, {"type": "input", "block_id": "application_id", "element": {"type": "plain_text_input", "action_id": "application_id", "initial_value": "6"}, "label": {"type": "plain_text", "text": "Application Id", "emoji": true}}, {"type": "input", "block_id": "next_link", "element": {"type": "plain_text_input", "action_id": "next_link", "initial_value": "https://localhost/applications?application_id=6"}, "label": {"type": "plain_text", "text": "Next Link", "emoji": true}}, {"type": "input", "block_id": "t_appid", "element": {"type": "plain_text_input", "action_id": "t_appid", "initial_value": "SVCD006"}, "label": {"type": "plain_text", "text": "t_AppID", "emoji": true}}, {"type": "input", "block_id": "application_description", "element": {"type": "plain_text_input", "action_id": "application_description", "initial_value": "Ride a Ostrich"}, "label": {"type": "plain_text", "text": "Application Description", "emoji": true}}, {"type": "input", "block_id": "line_of_business", "label": {"type": "plain_text", "text": "Line Of Business"}, "element": {"type": "external_select", "action_id": "line_of_business", "placeholder": {"type": "plain_text", "text": "Choose line_of_business"}, "min_query_length": 2}}, {"type": "input", "block_id": "create_date", "element": {"type": "datepicker", "action_id": "create_date", "initial_date": "1970-01-01", "placeholder": {"type": "plain_text", "text": "Choose a date", "emoji": true}, "confirm": {"title": {"type": "plain_text", "text": "Change Create Date Date"}, "text": {"type": "mrkdwn", "text": "Do you really want to change the Create Date date?"}, "confirm": {"type": "plain_text", "text": "Yes, I do!"}, "deny": {"type": "plain_text", "text": "No, I do not!"}}}, "label": {"type": "plain_text", "text": "Create Date", "emoji": true}}, {"type": "input", "block_id": "update_date", "element": {"type": "datepicker", "action_id": "update_date", "initial_date": "1970-01-01", "placeholder": {"type": "plain_text", "text": "Choose a date", "emoji": true}, "confirm": {"title": {"type": "plain_text", "text": "Change Update Date Date"}, "text": {"type": "mrkdwn", "text": "Do you really want to change the Update Date date?"}, "confirm": {"type": "plain_text", "text": "Yes, I do!"}, "deny": {"type": "plain_text", "text": "No, I do not!"}}}, "label": {"type": "plain_text", "text": "Update Date", "emoji": true}}, {"type": "input", "block_id": "application_budget_account_string", "element": {"type": "plain_text_input", "action_id": "application_budget_account_string", "initial_value": "budget$1"}, "label": {"type": "plain_text", "text": "Application Budget Account String", "emoji": true}}]'

        >>> json.dumps(data.to_slack_modal_form(regex_field_filters = [r'_id$',r'_link$',r'update_date'], slack_element_options = {'account_id': {'options': [], 'transform':[], 'initial_title': 'Atari', 'action_id': 'slater_selected_accounts' }, 'active': { 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'contact_id': {'options': [], 'transform':[], 'initial_title': 'Atari@root.net', 'action_id': 'slater_selected_contacts' }, 'decommissioned':{ 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'tower_id': {'options': [], 'transform':[], 'initial_title': 'Dark Tower', 'action_id': 'slater_selected_towers' }}))  # noqa E501
        '[{"type": "input", "block_id": "application_name", "element": {"type": "plain_text_input", "action_id": "application_name", "initial_value": "Joust"}, "label": {"type": "plain_text", "text": "Application Name", "emoji": true}}, {"type": "input", "block_id": "t_appid", "element": {"type": "plain_text_input", "action_id": "t_appid", "initial_value": "SVCD006"}, "label": {"type": "plain_text", "text": "t_AppID", "emoji": true}}, {"type": "input", "block_id": "application_description", "element": {"type": "plain_text_input", "action_id": "application_description", "initial_value": "Ride a Ostrich"}, "label": {"type": "plain_text", "text": "Application Description", "emoji": true}}, {"type": "input", "block_id": "create_date", "element": {"type": "datepicker", "action_id": "create_date", "initial_date": "1970-01-01", "placeholder": {"type": "plain_text", "text": "Choose a date", "emoji": true}, "confirm": {"title": {"type": "plain_text", "text": "Change Create Date Date"}, "text": {"type": "mrkdwn", "text": "Do you really want to change the Create Date date?"}, "confirm": {"type": "plain_text", "text": "Yes, I do!"}, "deny": {"type": "plain_text", "text": "No, I do not!"}}}, "label": {"type": "plain_text", "text": "Create Date", "emoji": true}}, {"type": "input", "block_id": "application_budget_account_string", "element": {"type": "plain_text_input", "action_id": "application_budget_account_string", "initial_value": "budget$1"}, "label": {"type": "plain_text", "text": "Application Budget Account String", "emoji": true}}]'

        >>> json.dumps(data.to_slack_modal_form(regex_field_filters = [r'application_id',r'_link$', r'update_date'], slack_element_type_map = { 'application_name': 'plain_text_input', 't_appid': 'plain_text_input', 'application_description': 'plain_text_input', 'line_of_business': 'external_select', 'create_date': 'datepicker', 'update_date': 'datepicker', 'application_budget_account_string': 'plain_text_input', 'active': 'radio_buttons', 'business_unit_id': 'external_select', 'pagerduty_url': 'plain_text_input', 'tower_id': 'external_select' }, field_names_remap={'lob_id':'line_of_business'}, slack_element_options = {'account_id': {'options': [], 'transform':[], 'initial_title': 'Atari', 'action_id': 'slater_selected_accounts' }, 'active': { 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'contact_id': {'options': [], 'transform':[], 'initial_title': 'Atari@root.net', 'action_id': 'slater_selected_contacts' }, 'decommissioned':{ 'options': [{'Yes':'0'},{'No':'1'}], 'transform': [{'0':'Yes'},{'1':'No'}] }, 'tower_id': {'options': [], 'transform':[], 'initial_title': 'Dark Tower', 'action_id': 'slater_selected_towers' }}))  # noqa E501
        '[{"type": "input", "block_id": "application_name", "element": {"type": "plain_text_input", "action_id": "application_name", "initial_value": "Joust"}, "label": {"type": "plain_text", "text": "Application Name", "emoji": true}}, {"type": "input", "block_id": "t_appid", "element": {"type": "plain_text_input", "action_id": "t_appid", "initial_value": "SVCD006"}, "label": {"type": "plain_text", "text": "t_AppID", "emoji": true}}, {"type": "input", "block_id": "application_description", "element": {"type": "plain_text_input", "action_id": "application_description", "initial_value": "Ride a Ostrich"}, "label": {"type": "plain_text", "text": "Application Description", "emoji": true}}, {"type": "input", "block_id": "line_of_business", "label": {"type": "plain_text", "text": "Line Of Business"}, "element": {"type": "external_select", "action_id": "line_of_business", "placeholder": {"type": "plain_text", "text": "Choose line_of_business"}, "min_query_length": 2}}, {"type": "input", "block_id": "create_date", "element": {"type": "datepicker", "action_id": "create_date", "initial_date": "1970-01-01", "placeholder": {"type": "plain_text", "text": "Choose a date", "emoji": true}, "confirm": {"title": {"type": "plain_text", "text": "Change Create Date Date"}, "text": {"type": "mrkdwn", "text": "Do you really want to change the Create Date date?"}, "confirm": {"type": "plain_text", "text": "Yes, I do!"}, "deny": {"type": "plain_text", "text": "No, I do not!"}}}, "label": {"type": "plain_text", "text": "Create Date", "emoji": true}}, {"type": "input", "block_id": "application_budget_account_string", "element": {"type": "plain_text_input", "action_id": "application_budget_account_string", "initial_value": "budget$1"}, "label": {"type": "plain_text", "text": "Application Budget Account String", "emoji": true}}]'

        """
        if not slack_element_type_map:
            slack_element_type_map = self.slack_element_type_map.get('DEFAULT')
        return super().to_slack_modal_form(regex_field_filters=regex_field_filters,
                                           field_names_remap=field_names_remap,
                                           field_sort_order=field_sort_order,
                                           slack_element_type_map=slack_element_type_map,
                                           slack_element_options=slack_element_options)

    def to_edit_form(self, entitlement_level: SLaterUserEntitlementLevel = None) -> Tuple:
        """ Convert our internal state to a slack json block

        >>> data = Application(**{'application_name': 'SWA Test App', 't_appid': 'PSI0001', 'application_description': 'Ride a Ostrich', 'lob_id': 1, 'create_date': '1970-01-01', 'application_budget_account_string': 'budget$1', 'active': 1, 'business_unit_id': 1, 'pagerduty_url': 'https://pearsonai.pagerduty.com/', 'tower_id': 1, 'application_id': 1, 'update_date': '1970-01-01', 'slater_update_user': 'jonathan.morris@pearson.com', 'next_link': 'https://localhost/applications?application_id=2', 'governance_lob_name': 'PSI Test Lob', 'business_unit_name': 'PSI Test Business Unit', 'tower_name': 'PSI Test Tower', 'tower_contact_id': 1, 'tower_contact_name': 'Jonathan Morris', 'tower_contact_email': 'jonathan.morris@pearson.com'}) # noqa E501

        >>> json.dumps(data.to_edit_form('SLATER_WRITER')) # noqa E501
        '[{"type": "input", "block_id": "application_name", "element": {"type": "plain_text_input", "action_id": "application_name", "initial_value": "SWA Test App"}, "label": {"type": "plain_text", "text": "Application Name", "emoji": true}}, {"type": "input", "block_id": "t_appid", "element": {"type": "plain_text_input", "action_id": "t_appid", "initial_value": "PSI0001"}, "label": {"type": "plain_text", "text": "t_AppID", "emoji": true}}, {"type": "input", "block_id": "application_description", "element": {"type": "plain_text_input", "action_id": "application_description", "initial_value": "Ride a Ostrich"}, "label": {"type": "plain_text", "text": "Application Description", "emoji": true}}, {"type": "input", "block_id": "tower_id", "label": {"type": "plain_text", "text": "Tower"}, "element": {"type": "external_select", "action_id": "slater_selected_towers", "placeholder": {"type": "plain_text", "text": "Choose tower_id"}, "min_query_length": 2}}, {"type": "input", "block_id": "business_unit_id", "label": {"type": "plain_text", "text": "Business Unit Name"}, "element": {"type": "external_select", "action_id": "slater_selected_business_units", "placeholder": {"type": "plain_text", "text": "Choose business_unit_id"}, "min_query_length": 2}}, {"type": "section", "block_id": "application_budget_account_string-title", "text": {"type": "mrkdwn", "text": "*Application Budget Account String*"}}, {"type": "section", "block_id": "application_budget_account_string", "text": {"type": "mrkdwn", "text": "budget$1"}}, {"type": "input", "block_id": "pagerduty_url", "element": {"type": "plain_text_input", "action_id": "pagerduty_url", "initial_value": "https://pearsonai.pagerduty.com/"}, "label": {"type": "plain_text", "text": "Pagerduty Url", "emoji": true}}, {"type": "input", "block_id": "active", "element": {"type": "radio_buttons", "options": [{"text": {"type": "plain_text", "text": "Yes"}, "value": "0"}, {"text": {"type": "plain_text", "text": "No"}, "value": "1"}], "initial_option": {"text": {"type": "plain_text", "text": "No"}, "value": "1"}}, "label": {"type": "plain_text", "text": "Active", "emoji": true}}]'
        """
        # Common params can be defined here
        regex_field_filters = [
            r'application_id',
            r'_link$',
            r'update_date',
            r'date_last_update',
            r'create_date',
            r'row_num',
            r'business_unit_name',
            r'tower_name',
            r'tower_contact_name',
            r'tower_contact_email',
            r'governance_lob_name',
            r'tower_contact_id',
            r'slater_update_user'
        ]
        skip_metadata_keys = [
            'date_last_update',
            'slater_update_user',
            'next_link',
            'governance_lob_name',
            'business_unit_name',
            'tower_name',
            'tower_contact_id',
            'tower_contact_name',
            'tower_contact_email',
            'row_num'
        ]
        field_sort_order = [
            'application_name',
            't_appid',
            'application_description',
            'tower_id',
            'business_unit_id',
            'slater_update_user',
            'application_budget_account_string',
            'pagerduty_url',
            'active'
        ]
        slack_element_options = {
            'active': {
                'options': [{'Yes': '0'}, {'No': '1'}],
                'transform': [{'0': 'Yes'}, {'1': 'No'}]
            },
            'lob_id': {
                'options': [],
                'transform': [],
                'initial_title': None,
                'action_id': 'slater_selected_lobs',
                'label': 'Governance Lob'
            },
            'tower_id': {
                'options': [],
                'transform': [],
                'initial_title': None,
                'action_id': 'slater_selected_towers',
                'label': 'Tower'
            },
            'business_unit_id': {
                'options': [],
                'transform': [],
                'initial_title': None,
                'action_id': 'slater_selected_business_units',
                'label': 'Business Unit Name'
            }
        }
        field_names_remap = None
        slack_element_type_map = self.slack_element_type_map.get(
            entitlement_level,
            self.slack_element_type_map['DEFAULT']
        )

        entitlement_config = {
            'DEFAULT': {
                'regex_field_filters': regex_field_filters,
                'field_names_remap': field_names_remap,
                'field_sort_order': field_sort_order,
                'slack_element_type_map': slack_element_type_map,
                'slack_element_options': slack_element_options,
                'skip_metadata_keys': skip_metadata_keys,
            },
            'SLATER_WRITER': {
                'regex_field_filters': regex_field_filters,
                'field_names_remap': field_names_remap,
                'field_sort_order': field_sort_order,
                'slack_element_type_map': slack_element_type_map,
                'slack_element_options': slack_element_options,
                'skip_metadata_keys': skip_metadata_keys,
            },
            'SLATER_CDS_WRITER': {
                'regex_field_filters': regex_field_filters,
                'field_names_remap': field_names_remap,
                'field_sort_order': field_sort_order,
                'slack_element_type_map': slack_element_type_map,
                'slack_element_options': slack_element_options,
                'skip_metadata_keys': skip_metadata_keys,
            },
            'SLATER_FINOPS_WRITER': {
                'regex_field_filters': regex_field_filters,
                'field_names_remap': field_names_remap,
                'field_sort_order': field_sort_order,
                'slack_element_type_map': slack_element_type_map,
                'slack_element_options': slack_element_options,
                'skip_metadata_keys': skip_metadata_keys,
            },
            'SLATER_ADMIN': {
                'regex_field_filters': regex_field_filters,
                'field_names_remap': field_names_remap,
                'field_sort_order': field_sort_order,
                'slack_element_type_map': slack_element_type_map,
                'slack_element_options': slack_element_options,
                'skip_metadata_keys': skip_metadata_keys,
            }
        }

        return super().to_slater_edit_form(**entitlement_config.get(
            entitlement_level,
            entitlement_config['DEFAULT'])
        )


@xray.capture()
class Applications(SLaterList):
    @xray.capture()
    def __init__(self, applications: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(applications, limit=limit, next_token=next_token)

    @xray.capture()
    def to_lobs_external_select(self) -> Tuple:
        select = {
            'options': []
        }
        for application in self.data:
            select.get('options').append(
                application.to_lobs_external_select_option())
        return select

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> applications = Applications([Application(application_name='Joust', application_id='6', next_link='https://localhost/applications?application_id=6', t_appid='SVCD006', application_description='Ride a Ostrich')])

        >>> data = json.loads(applications.to_json())

        >>> data['applications'][0]['application_name']
        'Joust'
        """
        return super().to_json(type_name='applications')

    @xray.capture()
    def to_csv(self):
        """ Test that csv data loads into a pandas dataframe
        >>> applications = Applications([Application(application_name='Joust', application_id='6', next_link='https://localhost/applications?application_id=6', t_appid='SVCD006', application_description='Ride a Ostrich', lob='foo', create_date='1970-01-01', update_date='1970-01-01', budget='budget$1')])
        >>> data = applications.to_csv().lstrip().splitlines()
        >>> assert (len(data) == 2), f'expected two rows (header and one data record), got {len(data)} rows'
        """
        return super().to_csv()


@xray.capture()
class ApplicationContact(SLaterObject):
    """
    >>> app_contact = ApplicationContact(application_id='1',contact_id='1', contact_type_id='1', contact_type='application_owner', next_link='https://localhost/applicationcontacts?contact_type_id=1', contact_email='dark.lord@darktower.com', create_date=None,update_date='2020-10-20')

    >>> app_contact.get('contact_type')
    'application_owner'
    """
    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:

        self.data = {}
        super().__init__(*args, **kwargs)

    @xray.capture()
    # override default find by id
    def to_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("contact_name").title()} ({self.data.get("application_name")} - {self.data.get("t_appid")})',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("application_contact_id")}'
        }

    @xray.capture()
    def to_email_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("contact_email").title()} ({self.data.get("application_name")} - {self.data.get("t_appid")})',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("application_contact_id")}'
        }


@xray.capture()
class ApplicationContacts(SLaterList):
    @xray.capture()
    def __init__(self, contacts: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(contacts, limit=limit, next_token=next_token)

    @xray.capture()
    def to_email_external_select(self) -> Tuple:
        select = {
            'options': []
        }
        for app_contact in self.data:
            select.get('options').append(
                app_contact.to_email_external_select_option())
        return select

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> app_contacts = ApplicationContacts([ApplicationContact(application_name='Dark Tower', t_appid='SVC00000', contact_name='Sauron DarkLord', contact_type='application_owner', next_link='https://localhost/applicationcontacts?application_contact_id=1', contact_email='dark.lord@darktower.com', create_date=None,update_date='2020-10-20')])

        >>> data = json.loads(app_contacts.to_json())

        >>> data['application_contacts'][0]['contact_name']
        'Sauron DarkLord'
        """
        return super().to_json(type_name='application_contacts')

    @xray.capture()
    def to_csv(self):
        """ Test that csv data loads into a pandas dataframe
        >>> app_contacts = ApplicationContacts([ApplicationContact(application_name='Dark Tower', t_appid='SVC00000', contact_name='Sauron DarkLord', contact_type='application_owner', next_link='https://localhost/applicationcontacts?application_contact_id=1', contact_email='dark.lord@darktower.com', create_date=None,update_date='2020-10-20')])

        >>> data = app_contacts.to_csv().lstrip().splitlines()

        >>> assert (len(data) == 2), f'expected two rows (header and one data record), got {len(data)} rows'
        """
        return super().to_csv()


@xray.capture()
class BatchError(SLaterObject):
    """ Validate we can create an batch model
    >>> os.environ['STACK_NAME'] = 'iobots-stack'
    >>> testing.export_secret_arns(os.environ['STACK_NAME'])
    >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())


    Validate retrieved batch
    >>> error = BatchError(batch_load_id = 4, cannot_process = 1, errors = '*t_appid not found in applications table *master_payer_name not found in master_payers table', row_num= 1, entity_type= 'account_error', cloud_account_number= '0123456789', cloud_account_name= 'sega', cloud_account_root_email= 'sonic@root.net', master_payer_name= 'SLT BU Master Payer 4', intake_number= 'APIIntake54321', account_type_name= 'SLT BU Prod', governance_lob_name= 'lob a', create_date= '2020-04-30', budget_account_string= 'budget$3', budget_alert_monthly_threshold= 500, project_number= 'apiProjNum', snow_approval_group= 'API Test SNOW approval group', t_appid= 'SVCD002', tower_name= 'SLT Tower 2')
    >>> error.get('batch_load_id')
    4
    >>> error.get('cannot_process')
    1
    >>> error.get('errors')
    '*t_appid not found in applications table *master_payer_name not found in master_payers table'
    >>> error.get('entity').get('cloud_account_number')
    '0123456789'
    """
    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:

        self.data = {}
        super().__init__(*args, **kwargs)

        search_key = kwargs.get('entity_type')

        try:
            for key in [
                'batch_load_id',
                'cannot_process',
                'errors',
                    'entity_type']:

                del kwargs[key]
        except Exception:
            pass  # trapping error

        self.__setitem__('entity', self.get_entity_type(search_key, **kwargs))

    @xray.capture()
    @staticmethod
    def get_entity_type(entity_type_name: str, **kwargs):
        entity_types: Dict = {
            "account_error": Account,
            "account_contact_error": Contact,
            "application_error": Application,
            "application_contact_error": Contact,
            "tower_error": Tower
        }

        if entity_type_name in entity_types:
            return entity_types[entity_type_name](**kwargs)

        return None


@xray.capture()
class BatchErrors(SLaterList):
    @xray.capture()
    def __init__(self, batch_errors: list, batch_load_id: int = -1, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(batch_errors, limit=limit, next_token=next_token)

        self.batch_load_id = batch_load_id

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> errors = BatchErrors([BatchError(batch_load_id = 4, cannot_process = 1, errors = '*t_appid not found in applications table *master_payer_name not found in master_payers table', row_num= 1, entity_type= 'account_error', cloud_account_number= '0123456789', cloud_account_name= 'sega', cloud_account_root_email= 'sonic@root.net', master_payer_name= 'SLT BU Master Payer 4', intake_number= 'APIIntake54321', account_type_name= 'SLT BU Prod', governance_lob_name= 'lob a', create_date= '2020-04-30', budget_account_string= 'budget$3', budget_alert_monthly_threshold= 500, project_number= 'apiProjNum', snow_approval_group= 'API Test SNOW approval group', t_appid= 'SVCD002', tower_name= 'SLT Tower 2')])
        >>> data = json.loads(errors.to_json())
        >>> # data
        >>> assert(data['batch_errors'][0]['cloud_account_number'] == '0123456789'), f"expected contact name: 'speed racer', got {data['batch_errors'][0]['name']}"
        """
        return super().to_json(type_name='batch_errors')

    @xray.capture()
    def to_csv(self, columns: list = None):
        """ Test that csv data loads into a pandas dataframe
        >>> errors = BatchErrors([BatchError(batch_load_id = 4, cannot_process = 1, errors = '*t_appid not found in applications table *master_payer_name not found in master_payers table', row_num= 1, entity_type= 'account_error', cloud_account_number= '0123456789', cloud_account_name= 'sega', cloud_account_root_email= 'sonic@root.net', master_payer_name= 'SLT BU Master Payer 4', intake_number= 'APIIntake54321', account_type_name= 'SLT BU Prod', governance_lob_name= 'lob a', create_date= '2020-04-30', budget_account_string= 'budget$3', budget_alert_monthly_threshold= 500, project_number= 'apiProjNum', snow_approval_group= 'API Test SNOW approval group', t_appid= 'SVCD002', tower_name= 'SLT Tower 2')])
        >>> data = errors.to_csv().lstrip().splitlines()
        >>> assert (len(data) == 2), f'expected two rows (header and one data record), got {len(data)} rows'
        """
        if columns is None:
            columns = [
                'batch_load_id',
                'cannot_process',
                'errors',
                'row_num',
                'entity_type'
            ]

            if self.data[0]['entity_type'] == 'account_contact_error':
                columns.extend([
                    'contact_type',
                    'contact_name',
                    'contact_email',
                    'cloud_account_number',
                ])

            if self.data[0]['entity_type'] == 'account_error':
                columns.extend([
                    'cloud_account_number',
                    'cloud_account_name',
                    'cloud_account_root_email',
                    'master_payer_name',
                    'intake_number',
                    'account_type_name',
                    'governance_lob_name',
                    'create_date',
                    'budget_account_string',
                    'budget_alert_monthly_threshold',
                    'project_number',
                    'snow_approval_group',
                    't_appid',
                    'tower_name'
                ])
        return super().to_csv(columns=columns)


@xray.capture()
class BatchLoad(SLaterObject):
    """ Validate we can create an batch model
    >>> os.environ['STACK_NAME'] = 'iobots-stack'
    >>> testing.export_secret_arns(os.environ['STACK_NAME'])
    >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())


    Validate retrieved batch
    >>> batch = BatchLoad(slater_update_user='john.torres',batch_load_id=339,next_link='https://localhost/batcherrors?batch_load_id=339',group_batch_load_id=118,s3_key='inbound/00010203-0405-0607-0809-0a0b0c0d0e0f/application_contacts.csv',status='Processing', load_date='2020-09-17', processed_date='2020-09-17', num_errors=0)
    >>> batch.get('group_batch_load_id')
    118
    >>> batch.get('slater_update_user')
    'john.torres'
    >>> batch.get('processed_date')
    '2020-09-17'
    """

    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:

        self.data = {}
        super().__init__(*args, **kwargs)

    @xray.capture()
    # override default find by id
    def to_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("name").title()} ({self.data.get("s3_key")})',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("id")}'
        }


@xray.capture()
class BatchLoads(SLaterList):
    @xray.capture()
    def __init__(self, batch_loads: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(batch_loads, limit=limit, next_token=next_token)

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> batches = BatchLoads([BatchLoad(slater_update_user='john.torres',batch_load_id=339,next_link='https://localhost/batcherrors?batch_load_id=339',group_batch_load_id=118,s3_key='inbound/00010203-0405-0607-0809-0a0b0c0d0e0f/application_contacts.csv',status='Processing', load_date='2020-09-17', processed_date='2020-09-17', num_errors=0)])

        >>> data = json.loads(batches.to_json())

        >>> data['batch_loads'][0]['slater_update_user']
        'john.torres'
        """
        return super().to_json(type_name='batch_loads')

    @xray.capture()
    def to_csv(self):
        """ Test that csv data loads into a pandas dataframe
        >>> batches = BatchLoads([BatchLoad(slater_update_user='john.torres',batch_load_id=339,next_link='https://localhost/batcherrors?batch_load_id=339',group_batch_load_id=118,s3_key='inbound/00010203-0405-0607-0809-0a0b0c0d0e0f/application_contacts.csv',status='Processing', load_date='2020-09-17', processed_date='2020-09-17', num_errors=0)])

        >>> data = batches.to_csv().lstrip().splitlines()

        >>> len(data)
        2
        """
        return super().to_csv()


@xray.capture()
class BatchImport(SLaterObject):
    """ Validate we can create an batch model
    >>> os.environ['STACK_NAME'] = 'iobots-stack'
    >>> testing.export_secret_arns(os.environ['STACK_NAME'])
    >>> slack_client = slack_sdk.WebClient(token=testing.slack_bearer_token())


    Validate retrieved batch
    >>> batch = BatchImport(slater_update_user='john.torres',group_batch_load_id=118,next_link='https://localhost/batchgroup?group_batch_id=118',group_batch_uuid='00010203-0405-0607-0809-0a0b0c0d0e0f',status='Processing', load_date='2020-09-17', processed_date='2020-09-17', num_errors=0, batch_loads=BatchLoads([BatchLoad(slater_update_user='john.torres',batch_load_id=339,next_link='https://localhost/batcherrors?batch_load_id=339',group_batch_load_id=118,s3_key='inbound/00010203-0405-0607-0809-0a0b0c0d0e0f/application_contacts.csv',status='Processing', load_date='2020-09-17', processed_date='2020-09-17', num_errors=0)]))
    >>> batch.get('group_batch_load_id')
    118
    >>> batch.get('slater_update_user')
    'john.torres'
    >>> batch.get('processed_date')
    '2020-09-17'
    >>> batch.get('batch_loads')[0].get('batch_load_id')
    339
    """
    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:

        self.data = {}
        super().__init__(*args, **kwargs)

    @xray.capture()
    def GetErrors(self):
        pass

    @xray.capture()
    # override default find by id
    def to_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("name").title()} ({self.data.get("group_batch_uuid")})',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("id")}'
        }


@xray.capture()
class BatchImports(SLaterList):
    @xray.capture()
    def __init__(self, batch_imports: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(batch_imports, limit=limit, next_token=next_token)

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> batches = BatchImports([BatchImport(slater_update_user='john.torres',group_batch_load_id=118,next_link='https://localhost/batchgroup?group_batch_id=118',group_batch_uuid='00010203-0405-0607-0809-0a0b0c0d0e0f',status='Processing', load_date='2020-09-17', processed_date='2020-09-17', num_errors=0, batch_loads=[])])

        >>> data = json.loads(batches.to_json())

        >>> data['batch_imports'][0]['slater_update_user']
        'john.torres'
        """
        return super().to_json(type_name='batch_imports')

    @xray.capture()
    def to_csv(self):
        """ Test that csv data loads into a pandas dataframe
        >>> batches = BatchImports([BatchImport(slater_update_user='john.torres',group_batch_load_id=118,next_link='https://localhost/batchgroup?group_batch_id=118',group_batch_uuid='00010203-0405-0607-0809-0a0b0c0d0e0f',status='Processing', load_date='2020-09-17', processed_date='2020-09-17', num_errors=0, batch_loads=BatchLoads([BatchLoad(slater_update_user='john.torres',batch_load_id=339,next_link='https://localhost/batcherrors?batch_load_id=339',group_batch_load_id=118,s3_key='inbound/00010203-0405-0607-0809-0a0b0c0d0e0f/application_contacts.csv',status='Processing', load_date='2020-09-17', processed_date='2020-09-17', num_errors=0)]) )])
        >>> data = batches.to_csv().lstrip().splitlines()
        >>> assert (len(data) == 2), f'expected two rows (header and one data record), got {len(data)} rows'
        """
        return super().to_csv()


@xray.capture()
class BusinessUnit(SLaterObject):
    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:

        self.data = {}
        super().__init__(*args, **kwargs)

        self.slack_element_type_map = {}
        default = {
            'business_unit_name': 'plain_text_input'
        }
        self.slack_element_type_map['DEFAULT'] = default
        self.slack_element_type_map['SLATER_ADMIN'] = default
        self.slack_element_type_map['SLATER_CDS_WRITER'] = default
        self.slack_element_type_map['SLATER_FINOPS_WRITER'] = default
        self.slack_element_type_map['SLATER_WRITER'] = default

    @xray.capture()
    # override our default which uses the 'id' field
    def to_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("business_unit_name").title()}',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("business_unit_id")}'
        }

    @xray.capture()
    def to_slack_modal_form(self,
                            regex_field_filters: List[str] = None,
                            field_names_remap: Dict[str, str] = None,
                            field_sort_order: List[str] = None,
                            slack_element_type_map: Dict[str, str] = None,
                            slack_element_options: Dict = None) -> Tuple:
        """ Convert our internal state to a slack json block

        >>> data = BusinessUnit(**{'business_unit_name': 'Assessments', 'business_unit_id': '1', 'next_link': 'https://localhost/businessunits?business_unit_id=1', 'create_date': '1970-01-01', 'update_date': '1970-01-01'}) # noqa E501

        >>> json.dumps(data.to_slack_modal_form())
        '[{"type": "input", "block_id": "business_unit_name", "element": {"type": "plain_text_input", "action_id": "business_unit_name", "initial_value": "Assessments"}, "label": {"type": "plain_text", "text": "Business Unit Name", "emoji": true}}, {"type": "input", "block_id": "business_unit_id", "element": {"type": "plain_text_input", "action_id": "business_unit_id", "initial_value": "1"}, "label": {"type": "plain_text", "text": "Business Unit Id", "emoji": true}}, {"type": "input", "block_id": "next_link", "element": {"type": "plain_text_input", "action_id": "next_link", "initial_value": "https://localhost/businessunits?business_unit_id=1"}, "label": {"type": "plain_text", "text": "Next Link", "emoji": true}}, {"type": "input", "block_id": "create_date", "element": {"type": "plain_text_input", "action_id": "create_date", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Create Date", "emoji": true}}, {"type": "input", "block_id": "update_date", "element": {"type": "plain_text_input", "action_id": "update_date", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Update Date", "emoji": true}}]'

        >>> json.dumps(data.to_slack_modal_form(regex_field_filters = [r'_id',r'_link$', r'update_date']))  # noqa E501
        '[{"type": "input", "block_id": "business_unit_name", "element": {"type": "plain_text_input", "action_id": "business_unit_name", "initial_value": "Assessments"}, "label": {"type": "plain_text", "text": "Business Unit Name", "emoji": true}}, {"type": "input", "block_id": "create_date", "element": {"type": "plain_text_input", "action_id": "create_date", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Create Date", "emoji": true}}]'

        """
        if not slack_element_type_map:
            slack_element_type_map = self.slack_element_type_map.get('DEFAULT')
        return super().to_slack_modal_form(regex_field_filters=regex_field_filters,
                                           field_names_remap=field_names_remap,
                                           field_sort_order=field_sort_order,
                                           slack_element_type_map=slack_element_type_map,
                                           slack_element_options=slack_element_options)


@xray.capture()
class BusinessUnits(SLaterList):
    def __init__(self, businessunits: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(businessunits, limit=limit, next_token=next_token)

    @xray.capture()
    def to_external_select(self) -> Tuple:
        select = {
            'options': []
        }
        for businessunit in self.data:
            select.get('options').append(
                businessunit.to_external_select_option())
        return select

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> businessunits = BusinessUnits([BusinessUnit(**{'business_unit_name': 'Assessments', 'business_unit_id': '1', 'next_link': 'https://localhost/businessunits?business_unit_id=1', 'create_date': '1970-01-01', 'update_date': '1970-01-01'})])

        >>> data = json.loads(businessunits.to_json())

        >>> data['businessunits'][0]['business_unit_name']
        'Assessments'
        """
        return super().to_json(type_name='businessunits')

    @xray.capture()
    def to_csv(self):
        """ Test that csv data loads into a pandas dataframe
        >>> businessunits = BusinessUnits([BusinessUnit(**{'business_unit_name': 'Assessments', 'business_unit_id': '1', 'next_link': 'https://localhost/businessunits?business_unit_id=1', 'create_date': '1970-01-01', 'update_date': '1970-01-01'})])
        >>> data = businessunits.to_csv().lstrip().splitlines()
        >>> assert (len(data) == 2), f'expected two rows (header and one data record), got {len(data)} rows'
        """
        return super().to_csv()


@xray.capture()
class Contact(SLaterObject):
    """
    >>> contact = Contact(**{'contact_name': 'speed racer', 'contact_id': '1', 'next_link': 'https://localhost/contacts?contact_id=1', 'contact_email': 'speed.racer@saturday.org', 'create_date': '1970-01-01', 'update_date': '1970-01-01'})

    >>> contact.get('contact_name')
    'speed racer'

    """
    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:

        self.data = {}
        super().__init__(*args, **kwargs)
        self.slack_element_type_map = {}
        default = {
            'contact_name': 'plain_text_input',
            'contact_email': 'external_select'
        }
        self.slack_element_type_map['DEFAULT'] = default
        self.slack_element_type_map['SLATER_ADMIN'] = default
        self.slack_element_type_map['SLATER_CDS_WRITER'] = default
        self.slack_element_type_map['SLATER_FINOPS_WRITER'] = default
        self.slack_element_type_map['SLATER_WRITER'] = default

    @xray.capture()
    # override default find by id
    def to_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("contact_name").title()}',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("contact_id")}'
        }

    @xray.capture()
    def to_email_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("contact_email").title()}',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("contact_id")}'
        }

    @xray.capture()
    def to_slack_modal_form(self,
                            regex_field_filters: List[str] = None,
                            field_names_remap: Dict[str, str] = None,
                            field_sort_order: List[str] = None,
                            slack_element_type_map: Dict[str, str] = None,
                            slack_element_options: Dict = None) -> Tuple:
        if not slack_element_type_map:
            slack_element_type_map = self.slack_element_type_map.get('DEFAULT')
        return super().to_slack_modal_form(regex_field_filters=regex_field_filters,
                                           field_names_remap=field_names_remap,
                                           field_sort_order=field_sort_order,
                                           slack_element_type_map=slack_element_type_map,
                                           slack_element_options=slack_element_options)


@xray.capture()
class Contacts(SLaterList):
    @xray.capture()
    def __init__(self, contacts: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(contacts, limit=limit, next_token=next_token)
        """ Convert our internal state to a slack json block

        >>> data = Contact(**{'contact_name': 'speed racer', 'contact_id': '1', 'next_link': 'https://localhost/contacts?contact_id=1', 'contact_email': 'speed.racer@saturday.org', 'create_date': '1970-01-01', 'update_date': '1970-01-01'}) # noqa E501

        >>> json.dumps(data.to_slack_modal_form())
         '[{"type": "input", "block_id": "SLaterApplicationEdit-application_name", "element": {"type": "plain_text_input", "initial_value": "Joust", "action_id": "application_name"}, "label": {"type": "plain_text", "text": "Application Name", "emoji": true}}, {"type": "input", "block_id": "SLaterApplicationEdit-application_id", "element": {"type": "plain_text_input", "initial_value": "6", "action_id": "application_id"}, "label": {"type": "plain_text", "text": "Application Id", "emoji": true}}, {"type": "input", "block_id": "SLaterApplicationEdit-next_link", "element": {"type": "plain_text_input", "initial_value": "https://localhost/applications?application_id=6", "action_id": "next_link"}, "label": {"type": "plain_text", "text": "Next Link", "emoji": true}}, {"type": "input", "block_id": "SLaterApplicationEdit-t_appid", "element": {"type": "plain_text_input", "initial_value": "SVCD006", "action_id": "t_appid"}, "label": {"type": "plain_text", "text": "T Appid", "emoji": true}}, {"type": "input", "block_id": "SLaterApplicationEdit-application_description", "element": {"type": "plain_text_input", "initial_value": "Ride a Ostrich", "action_id": "application_description"}, "label": {"type": "plain_text", "text": "Application Description", "emoji": true}}, {"type": "input", "block_id": "SLaterApplicationEdit-lob_id", "label": {"type": "plain_text", "text": "Lob Id"}, "element": {"type": "external_select", "action_id": "slater_selected_lobs", "placeholder": {"type": "plain_text", "text": "Choose lob_id"}, "min_query_length": 2, "initial_option": {"text": {"type": "plain_text", "text": "Assessments"}, "value": "1"}}}, {"type": "input", "block_id": "SLaterApplicationEdit-create_date", "element": {"type": "datepicker", "action_id": "create_date", "initial_date": "1970-01-01", "placeholder": {"type": "plain_text", "text": "Choose a date", "emoji": true}, "confirm": {"title": {"type": "plain_text", "text": "Change Create Date Date"}, "text": {"type": "mrkdwn", "text": "Do you really want to change the Create Date date?"}, "confirm": {"type": "plain_text", "text": "Yes, I do!"}, "deny": {"type": "plain_text", "text": "No, I do not!"}}}, "label": {"type": "plain_text", "text": "Create Date", "emoji": true}}, {"type": "input", "block_id": "SLaterApplicationEdit-update_date", "element": {"type": "datepicker", "action_id": "update_date", "initial_date": "1970-01-01", "placeholder": {"type": "plain_text", "text": "Choose a date", "emoji": true}, "confirm": {"title": {"type": "plain_text", "text": "Change Update Date Date"}, "text": {"type": "mrkdwn", "text": "Do you really want to change the Update Date date?"}, "confirm": {"type": "plain_text", "text": "Yes, I do!"}, "deny": {"type": "plain_text", "text": "No, I do not!"}}}, "label": {"type": "plain_text", "text": "Update Date", "emoji": true}}, {"type": "input", "block_id": "SLaterApplicationEdit-application_budget_account_string", "element": {"type": "plain_text_input", "initial_value": "budget$1", "action_id": "application_budget_account_string"}, "label": {"type": "plain_text", "text": "Application Budget Account String", "emoji": true}}]'

        >>> json.dumps(data.to_slack_modal_form(regex_field_filters = [r'_id',r'_link$', r'update_date']))  # noqa E501
         '[{"type": "input", "block_id": "SLaterApplicationEdit-application_name", "element": {"type": "plain_text_input", "initial_value": "Joust", "action_id": "application_name"}, "label": {"type": "plain_text", "text": "Application Name", "emoji": true}}, {"type": "input", "block_id": "SLaterApplicationEdit-t_appid", "element": {"type": "plain_text_input", "initial_value": "SVCD006", "action_id": "t_appid"}, "label": {"type": "plain_text", "text": "T Appid", "emoji": true}}, {"type": "input", "block_id": "SLaterApplicationEdit-application_description", "element": {"type": "plain_text_input", "initial_value": "Ride a Ostrich", "action_id": "application_description"}, "label": {"type": "plain_text", "text": "Application Description", "emoji": true}}, {"type": "input", "block_id": "SLaterApplicationEdit-line_of_business", "label": {"type": "plain_text", "text": "Line Of Business"}, "element": {"type": "external_select", "action_id": "line_of_business", "placeholder": {"type": "plain_text", "text": "Choose line_of_business"}, "min_query_length": 2}}, {"type": "input", "block_id": "SLaterApplicationEdit-create_date", "element": {"type": "datepicker", "action_id": "create_date", "initial_date": "1970-01-01", "placeholder": {"type": "plain_text", "text": "Choose a date", "emoji": true}, "confirm": {"title": {"type": "plain_text", "text": "Change Create Date Date"}, "text": {"type": "mrkdwn", "text": "Do you really want to change the Create Date date?"}, "confirm": {"type": "plain_text", "text": "Yes, I do!"}, "deny": {"type": "plain_text", "text": "No, I do not!"}}}, "label": {"type": "plain_text", "text": "Create Date", "emoji": true}}, {"type": "input", "block_id": "SLaterApplicationEdit-application_budget_account_string", "element": {"type": "plain_text_input", "initial_value": "budget$1", "action_id": "application_budget_account_string"}, "label": {"type": "plain_text", "text": "Application Budget Account String", "emoji": true}}]'

        """

    @xray.capture()
    def to_email_external_select(self) -> Tuple:
        select = {
            'options': []
        }
        for contact in self.data:
            select.get('options').append(
                contact.to_email_external_select_option())
        return select

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> contacts = Contacts([Contact(**{'contact_name': 'speed racer', 'contact_id': '1', 'next_link': 'https://localhost/contacts?contact_id=1', 'contact_email': 'speed.racer@saturday.org', 'create_date': '1970-01-01', 'update_date': '1970-01-01'})])

        >>> data = json.loads(contacts.to_json())

        >>> data['contacts'][0]['contact_name']
        'speed racer'
        """
        return super().to_json(type_name='contacts')

    @xray.capture()
    def to_csv(self):
        """ Test that csv data loads into a pandas dataframe
        >>> contacts = Contacts([Contact(**{'contact_name': 'speed racer', 'contact_id': '1', 'next_link': 'https://localhost/contacts?contact_id=1', 'contact_email': 'speed.racer@saturday.org', 'create_date': '1970-01-01', 'update_date': '1970-01-01'})])

        >>> data = contacts.to_csv().lstrip().splitlines()

        >>> assert (len(data) == 2), f'expected two rows (header and one data record), got {len(data)} rows'
        """
        return super().to_csv()


@xray.capture()
class ContactType(SLaterObject):
    """
    >>> contact_type = ContactType(contact_type='Duckie', contact_type_id='124', next_link='https://localhost/contacttypes?contact_id=124', create_date='2020-07-22', update_date='2020-07-23')

    >>> contact_type.get('contact_type')
    'Duckie'

    """

    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:

        self.data = {}
        super().__init__(*args, **kwargs)
        self.slack_element_type_map = {}
        default = {
            'contact_type': 'plain_text_input'
        }
        self.slack_element_type_map['DEFAULT'] = default
        self.slack_element_type_map['SLATER_ADMIN'] = default
        self.slack_element_type_map['SLATER_CDS_WRITER'] = default
        self.slack_element_type_map['SLATER_FINOPS_WRITER'] = default
        self.slack_element_type_map['SLATER_WRITER'] = default

    @xray.capture()
    # override default find by id
    def to_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("contact_type").title()}',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("contact_type_id")}'
        }

    @xray.capture()
    def to_slack_modal_form(self,
                            regex_field_filters: List[str] = None,
                            field_names_remap: Dict[str, str] = None,
                            field_sort_order: List[str] = None,
                            slack_element_type_map: Dict[str, str] = None,
                            slack_element_options: Dict = None) -> Tuple:
        """ Convert our internal state to a slack json block

        >>> data = ContactType(**{'contact_type': 'Duckie', 'contact_type_id': '124', 'next_link': 'https://localhost/contacttypes?contact_type_id=1', 'create_date': '1970-01-01', 'update_date': '1970-01-01'}) # noqa E501

        >>> json.dumps(data.to_slack_modal_form())
        '[{"type": "input", "block_id": "contact_type", "element": {"type": "plain_text_input", "action_id": "contact_type", "initial_value": "Duckie"}, "label": {"type": "plain_text", "text": "Contact Type", "emoji": true}}, {"type": "input", "block_id": "contact_type_id", "element": {"type": "plain_text_input", "action_id": "contact_type_id", "initial_value": "124"}, "label": {"type": "plain_text", "text": "Contact Type Id", "emoji": true}}, {"type": "input", "block_id": "next_link", "element": {"type": "plain_text_input", "action_id": "next_link", "initial_value": "https://localhost/contacttypes?contact_type_id=1"}, "label": {"type": "plain_text", "text": "Next Link", "emoji": true}}, {"type": "input", "block_id": "create_date", "element": {"type": "plain_text_input", "action_id": "create_date", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Create Date", "emoji": true}}, {"type": "input", "block_id": "update_date", "element": {"type": "plain_text_input", "action_id": "update_date", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Update Date", "emoji": true}}]'

        >>> json.dumps(data.to_slack_modal_form(regex_field_filters = [r'_id',r'_link$', r'update_date']))  # noqa E501
        '[{"type": "input", "block_id": "contact_type", "element": {"type": "plain_text_input", "action_id": "contact_type", "initial_value": "Duckie"}, "label": {"type": "plain_text", "text": "Contact Type", "emoji": true}}, {"type": "input", "block_id": "create_date", "element": {"type": "plain_text_input", "action_id": "create_date", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Create Date", "emoji": true}}]'

        """
        if not slack_element_type_map:
            slack_element_type_map = self.slack_element_type_map.get('DEFAULT')
        return super().to_slack_modal_form(regex_field_filters=regex_field_filters,
                                           field_names_remap=field_names_remap,
                                           field_sort_order=field_sort_order,
                                           slack_element_type_map=slack_element_type_map,
                                           slack_element_options=slack_element_options)


@xray.capture()
class ContactTypes(SLaterList):
    def __init__(self, contacts: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(contacts, limit=limit, next_token=next_token)

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> contact_types = ContactTypes([ContactType(contact_type='Duckie', contact_type_id='124', next_link='https://localhost/contacttypes?contact_type_id=124', create_date='2020-07-22', update_date='2020-07-23')])

        >>> data = json.loads(contact_types.to_json())

        >>> data['contact_types'][0]['contact_type']
        'Duckie'
        """
        return super().to_json(type_name='contact_types')

    @xray.capture()
    def to_csv(self):
        """ Test that csv data loads into a pandas dataframe
        >>> contact_types = ContactTypes([ContactType(contact_type='Duckie', contact_type_id='124', next_link='https://localhost/contacttypes?contact_type_id=124', create_date='2020-07-22', update_date='2020-07-23')])

        >>> data = contact_types.to_csv().lstrip().splitlines()

        >>> assert (len(data) == 2), f'expected two rows (header and one data record), got {len(data)} rows'
        """
        return super().to_csv()

@xray.capture()
class CustomField(SLaterObject):
    """
    >>> custom_field = CustomField(custom_field_name='upc_code', for_account_use=1, for_application_use=1, custom_field_name_id=16, next_link='https://localhost/customfields?custom_field_name=upc_code', create_date='2021-03-19', date_last_update='2021-03-19', slater_update_user='Walling, Jerry (pcm_admin)', row_num=7)

    >>> custom_field.get('custom_field_name')
    'upc_code'

    """

    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:

        self.data = {}
        super().__init__(*args, **kwargs)

    @xray.capture()
    def to_slack_modal_form(self,
                            regex_field_filters: List[str] = None,
                            field_names_remap: Dict[str, str] = None,
                            field_sort_order: List[str] = None,
                            slack_element_type_map: Dict[str, str] = None,
                            slack_element_options: Dict = None) -> Tuple:
        """ Convert our internal state to a slack json block

        >>> data = CustomField(**{'custom_field_name': 'upc_code', 'for_account_use': 1, 'for_application_use': 1, 'custom_field_name_id': 16, 'next_link': 'https://localhost/customfields?custom_field_name=upc_code', 'create_date': '2021-03-19', 'date_last_update': '2021-03-19', 'slater_update_user': 'Walling, Jerry (pcm_admin)', 'row_num': 7}) # noqa E501

        >>> json.dumps(data.to_slack_modal_form())
        '[{"type": "input", "block_id": "custom_field_name", "element": {"type": "plain_text_input", "action_id": "custom_field_name", "initial_value": "upc_code"}, "label": {"type": "plain_text", "text": "Custom Field Name", "emoji": true}}, {"type": "input", "block_id": "for_account_use", "element": {"type": "plain_text_input", "action_id": "for_account_use", "initial_value": "1"}, "label": {"type": "plain_text", "text": "For Account Use", "emoji": true}}, {"type": "input", "block_id": "for_application_use", "element": {"type": "plain_text_input", "action_id": "for_application_use", "initial_value": "1"}, "label": {"type": "plain_text", "text": "For Application Use", "emoji": true}}, {"type": "input", "block_id": "custom_field_name_id", "element": {"type": "plain_text_input", "action_id": "custom_field_name_id", "initial_value": "16"}, "label": {"type": "plain_text", "text": "Custom Field Name Id", "emoji": true}}, {"type": "input", "block_id": "next_link", "element": {"type": "plain_text_input", "action_id": "next_link", "initial_value": "https://localhost/customfields?custom_field_name=upc_code"}, "label": {"type": "plain_text", "text": "Next Link", "emoji": true}}, {"type": "input", "block_id": "create_date", "element": {"type": "plain_text_input", "action_id": "create_date", "initial_value": "2021-03-19"}, "label": {"type": "plain_text", "text": "Create Date", "emoji": true}}, {"type": "input", "block_id": "date_last_update", "element": {"type": "plain_text_input", "action_id": "date_last_update", "initial_value": "2021-03-19"}, "label": {"type": "plain_text", "text": "Date Last Update", "emoji": true}}, {"type": "input", "block_id": "slater_update_user", "element": {"type": "plain_text_input", "action_id": "slater_update_user", "initial_value": "Walling, Jerry (pcm_admin)"}, "label": {"type": "plain_text", "text": "Slater Update User", "emoji": true}}, {"type": "input", "block_id": "row_num", "element": {"type": "plain_text_input", "action_id": "row_num", "initial_value": "7"}, "label": {"type": "plain_text", "text": "Row Num", "emoji": true}}]'

        >>> json.dumps(data.to_slack_modal_form(regex_field_filters = [r'_id',r'_link$', r'date_last_update']))  # noqa E501
        '[{"type": "input", "block_id": "custom_field_name", "element": {"type": "plain_text_input", "action_id": "custom_field_name", "initial_value": "upc_code"}, "label": {"type": "plain_text", "text": "Custom Field Name", "emoji": true}}, {"type": "input", "block_id": "for_account_use", "element": {"type": "plain_text_input", "action_id": "for_account_use", "initial_value": "1"}, "label": {"type": "plain_text", "text": "For Account Use", "emoji": true}}, {"type": "input", "block_id": "for_application_use", "element": {"type": "plain_text_input", "action_id": "for_application_use", "initial_value": "1"}, "label": {"type": "plain_text", "text": "For Application Use", "emoji": true}}, {"type": "input", "block_id": "create_date", "element": {"type": "plain_text_input", "action_id": "create_date", "initial_value": "2021-03-19"}, "label": {"type": "plain_text", "text": "Create Date", "emoji": true}}, {"type": "input", "block_id": "slater_update_user", "element": {"type": "plain_text_input", "action_id": "slater_update_user", "initial_value": "Walling, Jerry (pcm_admin)"}, "label": {"type": "plain_text", "text": "Slater Update User", "emoji": true}}, {"type": "input", "block_id": "row_num", "element": {"type": "plain_text_input", "action_id": "row_num", "initial_value": "7"}, "label": {"type": "plain_text", "text": "Row Num", "emoji": true}}]'

        """
        if not slack_element_type_map:
            slack_element_type_map = self.slack_element_type_map.get('DEFAULT')
        return super().to_slack_modal_form(regex_field_filters=regex_field_filters,
                                           field_names_remap=field_names_remap,
                                           field_sort_order=field_sort_order,
                                           slack_element_type_map=slack_element_type_map,
                                           slack_element_options=slack_element_options)


@xray.capture()
class CustomFields(SLaterList):
    def __init__(self, custom_fields: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(custom_fields, limit=limit, next_token=next_token)

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> custom_fields = CustomFields([CustomField(custom_field_name='upc_code', for_account_use=1, for_application_use=1, custom_field_name_id=16, next_link='https://localhost/customfields?custom_field_name=upc_code', create_date='2021-03-19', date_last_update='2021-03-19', slater_update_user='Walling, Jerry (pcm_admin)', row_num=7)])

        >>> data = json.loads(custom_fields.to_json())

        >>> data['custom_fields'][0]['custom_field_name']
        'upc_code'
        """
        return super().to_json(type_name='custom_fields')

    @xray.capture()
    def to_csv(self):
        """ Test that csv data loads into a pandas dataframe
        >>> custom_fields = CustomFields([CustomField(custom_field_name='upc_code', for_account_use=1, for_application_use=1, custom_field_name_id=16, next_link='https://localhost/customfields?custom_field_name=upc_code', create_date='2021-03-19', date_last_update='2021-03-19', slater_update_user='Walling, Jerry (pcm_admin)', row_num=7)])

        >>> data = custom_fields.to_csv().lstrip().splitlines()

        >>> assert (len(data) == 2), f'expected two rows (header and one data record), got {len(data)} rows'
        """
        return super().to_csv()


@xray.capture()
class Export(SLaterObject):
    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:

        self.data = {}
        super().__init__(*args, **kwargs)


@xray.capture()
class Exports(SLaterList):
    @xray.capture()
    def __init__(self, exports: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(exports, limit=limit, next_token=next_token)

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> exports = Exports([Export(cloud_account_name='Atari', cloud_account_id=1, cloud_account_number='0423456789', next_link='https://localhost/accounts/0423456789', cloud_account_description='japan-born', master_payer_ident=222222222222,environment_type_id=1,environment_type_name='non-production', cloud_account_root_email="john.torres@pearson.com",governance_lob_name="lob a", lob_id=1,budget_account_string="foo$1",decommissioned=0,provisioner="cinch",project_number="apifoo",snow_approval_group="slater snow",budget_alert_monthly_threshold=500, cloud_organization_id=1, cloud_organization_name="Assessments")])

        >>> data = json.loads(exports.to_json())

        >>> data['exports'][0]['cloud_account_name']
        'Atari'
        """
        return super().to_json(type_name='exports')

    @xray.capture()
    def to_csv(self, index: bool = False, columns: list = None):
        """ Test that csv data loads into a pandas dataframe
        >>> exports = Accounts([Export(cloud_account_name='Atari', cloud_account_id=1, cloud_account_number='0423456789', next_link='https://localhost/accounts/0423456789', cloud_account_description='japan-born', master_payer_ident=222222222222,environment_type_id=1,environment_type_name='non-production', cloud_account_root_email="john.torres@pearson.com",governance_lob_name="lob a", lob_id=1,budget_account_string="foo$1",decommissioned=0,provisioner="cinch",project_number="apifoo",snow_approval_group="slater snow",budget_alert_monthly_threshold=500, cloud_organization_id=1, cloud_organization_name="Assessments")])

        >>> data = exports.to_csv().lstrip().splitlines()

        >>> len(data)
        2
        """
        if not columns:
            columns = []
        return super().to_csv(index=index, columns=columns)


@xray.capture()
class LOB(SLaterObject):
    """
    >>> lob = LOB(governance_lob_name='Assessments', lob_id='1', next_link='https://localhost/lobs?lob_id=1', create_date='2020-07-22',update_date='2020-07-23')

    >>> lob.get('governance_lob_name')
    'Assessments'

    """

    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:
        self.data = {}
        super().__init__(*args, **kwargs)

        self.slack_element_type_map = {}
        default = {
            'governance_lob_name': 'plain_text_input'
        }
        self.slack_element_type_map['DEFAULT'] = default
        self.slack_element_type_map['SLATER_ADMIN'] = default
        self.slack_element_type_map['SLATER_CDS_WRITER'] = default
        self.slack_element_type_map['SLATER_FINOPS_WRITER'] = default
        self.slack_element_type_map['SLATER_WRITER'] = default

    @xray.capture()
    # override default find by id
    def to_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("governance_lob_name").title()}',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("lob_id")}'
        }

    @xray.capture()
    def to_slack_modal_form(self,
                            regex_field_filters: List[str] = None,
                            field_names_remap: Dict[str, str] = None,
                            field_sort_order: List[str] = None,                            slack_element_type_map: Dict[str, str] = None,
                            slack_element_options: Dict = None) -> Tuple:
        """ Convert our internal state to a slack json block

        >>> data = LOB(**{'governance_lob_name': 'Eiffel Tower', 'lob_id':'1', 'next_link': 'https://localhost/towers?tower_id=1', 'date_last_update': '1970-01-01','contact_id':'1'}) # noqa E501

        >>> json.dumps(data.to_slack_modal_form())
        '[{"type": "input", "block_id": "governance_lob_name", "element": {"type": "plain_text_input", "action_id": "governance_lob_name", "initial_value": "Eiffel Tower"}, "label": {"type": "plain_text", "text": "Governance Lob Name", "emoji": true}}, {"type": "input", "block_id": "lob_id", "element": {"type": "plain_text_input", "action_id": "lob_id", "initial_value": "1"}, "label": {"type": "plain_text", "text": "Lob Id", "emoji": true}}, {"type": "input", "block_id": "next_link", "element": {"type": "plain_text_input", "action_id": "next_link", "initial_value": "https://localhost/towers?tower_id=1"}, "label": {"type": "plain_text", "text": "Next Link", "emoji": true}}, {"type": "input", "block_id": "date_last_update", "element": {"type": "plain_text_input", "action_id": "date_last_update", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Date Last Update", "emoji": true}}, {"type": "input", "block_id": "contact_id", "element": {"type": "plain_text_input", "action_id": "contact_id", "initial_value": "1"}, "label": {"type": "plain_text", "text": "Contact Id", "emoji": true}}]'

        >>> json.dumps(data.to_slack_modal_form(regex_field_filters = [r'_id',r'_link$', r'update_date']))  # noqa E501
        '[{"type": "input", "block_id": "governance_lob_name", "element": {"type": "plain_text_input", "action_id": "governance_lob_name", "initial_value": "Eiffel Tower"}, "label": {"type": "plain_text", "text": "Governance Lob Name", "emoji": true}}, {"type": "input", "block_id": "date_last_update", "element": {"type": "plain_text_input", "action_id": "date_last_update", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Date Last Update", "emoji": true}}]'

        """
        if not slack_element_type_map:
            slack_element_type_map = self.slack_element_type_map.get('DEFAULT')
        return super().to_slack_modal_form(regex_field_filters=regex_field_filters,
                                           field_names_remap=field_names_remap,
                                           field_sort_order=field_sort_order,
                                           slack_element_type_map=slack_element_type_map,
                                           slack_element_options=slack_element_options)


@xray.capture()
class LOBs(SLaterList):
    @xray.capture()
    def __init__(self, lobs: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(lobs, limit=limit, next_token=next_token)

    @xray.capture()
    def to_external_select(self) -> Tuple:
        select = {
            'options': []
        }
        for lob in self.data:
            select.get('options').append(
                lob.to_external_select_option())
        return select

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> lobs = LOBs([LOB(governance_lob_name='speed racer', lob_id='1', next_link='https://localhost/lobs?lob_id=1', create_date='2020-07-22', update_date='2020-07-23')])

        >>> data = json.loads(lobs.to_json())

        >>> data['lobs'][0]['governance_lob_name']
        'speed racer'
        """
        return super().to_json(type_name='lobs')

    @xray.capture()
    def to_csv(self):
        """ Test that csv data loads into a pandas dataframe
        >>> lobs = LOBs([LOB(governance_lob_name='speed racer', lob_id='1', ref='https://localhost/lobs?lob_id=1', created='2020-07-22',updated='2020-07-23')])

        >>> data = lobs.to_csv().lstrip().splitlines()

        >>> assert (len(data) == 2), f'expected two rows (header and one data record), got {len(data)} rows'
        """
        return super().to_csv()


@xray.capture()
class MasterPayer(SLaterObject):
    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:

        self.data = {}
        super().__init__(*args, **kwargs)

        self.slack_element_type_map = {}
        default = {
            'master_payer_ident': 'plain_text_input',
            'master_payer_name': 'plain_text_input'
        }
        self.slack_element_type_map['DEFAULT'] = default
        self.slack_element_type_map['SLATER_ADMIN'] = default
        self.slack_element_type_map['SLATER_CDS_WRITER'] = default
        self.slack_element_type_map['SLATER_FINOPS_WRITER'] = default
        self.slack_element_type_map['SLATER_WRITER'] = default

    @xray.capture()
    # override our default which uses the 'id' field
    def to_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("master_payer_name").title()}',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("master_payer_id")}'
        }

    @xray.capture()
    def to_ident_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("master_payer_name").title()}',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("master_payer_ident")}'
        }

    @xray.capture()
    def to_slack_modal_form(self,
                            regex_field_filters: List[str] = None,
                            field_names_remap: Dict[str, str] = None,
                            field_sort_order: List[str] = None,
                            slack_element_type_map: Dict[str, str] = None,
                            slack_element_options: Dict = None) -> Tuple:
        """ Convert our internal state to a slack json block

        >>> data = MasterPayer(**{'master_payer_ident': '111111111111', 'master_payer_name': 'SLT BU Master Payer 1', 'master_payer_id':'1', 'next_link': 'https://localhost/masterpayers?master_payer_id=1', 'date_last_update': '1970-01-01'}) # noqa E501

        >>> json.dumps(data.to_slack_modal_form())
        '[{"type": "input", "block_id": "master_payer_ident", "element": {"type": "plain_text_input", "action_id": "master_payer_ident", "initial_value": "111111111111"}, "label": {"type": "plain_text", "text": "Master Payer Ident", "emoji": true}}, {"type": "input", "block_id": "master_payer_name", "element": {"type": "plain_text_input", "action_id": "master_payer_name", "initial_value": "SLT BU Master Payer 1"}, "label": {"type": "plain_text", "text": "Master Payer Name", "emoji": true}}, {"type": "input", "block_id": "master_payer_id", "element": {"type": "plain_text_input", "action_id": "master_payer_id", "initial_value": "1"}, "label": {"type": "plain_text", "text": "Master Payer Id", "emoji": true}}, {"type": "input", "block_id": "next_link", "element": {"type": "plain_text_input", "action_id": "next_link", "initial_value": "https://localhost/masterpayers?master_payer_id=1"}, "label": {"type": "plain_text", "text": "Next Link", "emoji": true}}, {"type": "input", "block_id": "date_last_update", "element": {"type": "plain_text_input", "action_id": "date_last_update", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Date Last Update", "emoji": true}}]'

        >>> json.dumps(data.to_slack_modal_form(regex_field_filters = [r'_id',r'_link$', r'update_date']))  # noqa E501
        '[{"type": "input", "block_id": "master_payer_name", "element": {"type": "plain_text_input", "action_id": "master_payer_name", "initial_value": "SLT BU Master Payer 1"}, "label": {"type": "plain_text", "text": "Master Payer Name", "emoji": true}}, {"type": "input", "block_id": "date_last_update", "element": {"type": "plain_text_input", "action_id": "date_last_update", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Date Last Update", "emoji": true}}]'

        """
        if not slack_element_type_map:
            slack_element_type_map = self.slack_element_type_map.get('DEFAULT')
        return super().to_slack_modal_form(regex_field_filters=regex_field_filters,
                                           field_names_remap=field_names_remap,
                                           field_sort_order=field_sort_order,
                                           slack_element_type_map=slack_element_type_map,
                                           slack_element_options=slack_element_options)


@xray.capture()
class MasterPayers(SLaterList):
    @xray.capture()
    def __init__(self, masterpayers: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(masterpayers, limit=limit, next_token=next_token)

    @xray.capture()
    def to_external_select(self) -> Tuple:
        select = {
            'options': []
        }
        for masterpayer in self.data:
            select.get('options').append(
                masterpayer.to_external_select_option())
        return select

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> masterpayers = MasterPayers([MasterPayer(**{'master_payer_ident': '111111111111', 'master_payer_name': 'SLT BU Master Payer 1', 'master_payer_id':'1', 'next_link': 'https://localhost/masterpayers?master_payer_id=1', 'date_last_update': '1970-01-01'})])

        >>> data = json.loads(masterpayers.to_json())

        >>> data['masterpayers'][0]['master_payer_name']
        'SLT BU Master Payer 1'
        """
        return super().to_json(type_name='masterpayers')

    @xray.capture()
    def to_csv(self):
        """ Test that csv data loads into a pandas dataframe
        >>> masterpayers = MasterPayers([MasterPayer(**{'master_payer_ident': '111111111111', 'master_payer_name': 'SLT BU Master Payer 1', 'master_payer_id':'1', 'next_link': 'https://localhost/masterpayers?master_payer_id=1', 'date_last_update': '1970-01-01'})])
        >>> data = masterpayers.to_csv().lstrip().splitlines()
        >>> assert (len(data) == 2), f'expected two rows (header and one data record), got {len(data)} rows'
        """
        return super().to_csv()


class SRE(SLaterObject):
    def __init__(self, *args, **kwargs) -> None:

        self.data = {}
        super().__init__(*args, **kwargs)

    # override default find by id
    def to_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("name").title()} ({self.data.get("email")})',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("id")}'
        }

    def to_email_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("email").title()} ({self.data.get("name")})',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("id")}'
        }


class SREs(SLaterList):
    def __init__(self, sres: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(sres, limit=limit, next_token=next_token)

    def to_email_external_select(self) -> Tuple:
        select = {
            'options': []
        }
        for tower in self.data:
            select.get('options').append(
                tower.to_email_external_select_option())
        return select

    def to_json(self):
        """ Test that json loads into an object properly
        >>> sres = SREs([SRE(contact_name='speed racer', contact_id=1, slater_update_user='john.torres', next_link='https://localhost/sres?contact_id=1', row_num=1)])

        >>> data = json.loads(sres.to_json())

        >>> data['sres'][0]['contact_name']
        'speed racer'
        """
        return super().to_json(type_name='sres')

    def to_csv(self):
        """ Test that csv data loads into a pandas dataframe
        >>> sres = SREs([SRE(contact_name='speed racer', contact_id=1, slater_update_user='john.torres', next_link='https://localhost/contacts?contact_id=1', row_num=1)])

        >>> data = sres.to_csv().lstrip().splitlines()

        >>> assert (len(data) == 2), f'expected two rows (header and one data record), got {len(data)} rows'
        """
        return super().to_csv()


@xray.capture()
class Tower(SLaterObject):
    @xray.capture()
    def __init__(self, *args, **kwargs) -> None:

        self.data = {}
        super().__init__(*args, **kwargs)

        self.slack_element_type_map = {}
        default = {
            'tower_name': 'plain_text_input',
            'contact_id': 'external_select'
        }
        self.slack_element_type_map['DEFAULT'] = default
        self.slack_element_type_map['SLATER_ADMIN'] = default
        self.slack_element_type_map['SLATER_CDS_WRITER'] = default
        self.slack_element_type_map['SLATER_FINOPS_WRITER'] = default
        self.slack_element_type_map['SLATER_WRITER'] = default

    @xray.capture()
    # override our default which uses the 'id' field
    def to_external_select_option(self):
        return {
            'text': {
                'text': f'{self.data.get("tower_name").title()}',
                'type': 'plain_text'
            },
            'value': f'{self.data.get("tower_id")}'
        }

    @xray.capture()
    def to_slack_modal_form(self,
                            regex_field_filters: List[str] = None,
                            field_names_remap: Dict[str, str] = None,
                            field_sort_order: List[str] = None,
                            slack_element_type_map: Dict[str, str] = None,
                            slack_element_options: Dict = None) -> Tuple:
        """ Convert our internal state to a slack json block

        >>> data = Tower(**{'tower_name': 'Eiffel Tower', 'tower_id':'1', 'next_link': 'https://localhost/towers?tower_id=1', 'date_last_update': '1970-01-01','contact_id':'1'}) # noqa E501

        >>> json.dumps(data.to_slack_modal_form())
        '[{"type": "input", "block_id": "tower_name", "element": {"type": "plain_text_input", "action_id": "tower_name", "initial_value": "Eiffel Tower"}, "label": {"type": "plain_text", "text": "Tower Name", "emoji": true}}, {"type": "input", "block_id": "tower_id", "element": {"type": "plain_text_input", "action_id": "tower_id", "initial_value": "1"}, "label": {"type": "plain_text", "text": "Tower Id", "emoji": true}}, {"type": "input", "block_id": "next_link", "element": {"type": "plain_text_input", "action_id": "next_link", "initial_value": "https://localhost/towers?tower_id=1"}, "label": {"type": "plain_text", "text": "Next Link", "emoji": true}}, {"type": "input", "block_id": "date_last_update", "element": {"type": "plain_text_input", "action_id": "date_last_update", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Date Last Update", "emoji": true}}, {"type": "input", "block_id": "contact_id", "label": {"type": "plain_text", "text": "Contact Id"}, "element": {"type": "external_select", "action_id": "contact_id", "placeholder": {"type": "plain_text", "text": "Choose contact_id"}, "min_query_length": 2}}]'

        >>> json.dumps(data.to_slack_modal_form(regex_field_filters = [r'_id',r'_link$', r'update_date']))  # noqa E501
        '[{"type": "input", "block_id": "tower_name", "element": {"type": "plain_text_input", "action_id": "tower_name", "initial_value": "Eiffel Tower"}, "label": {"type": "plain_text", "text": "Tower Name", "emoji": true}}, {"type": "input", "block_id": "date_last_update", "element": {"type": "plain_text_input", "action_id": "date_last_update", "initial_value": "1970-01-01"}, "label": {"type": "plain_text", "text": "Date Last Update", "emoji": true}}]'

        """
        if not slack_element_type_map:
            slack_element_type_map = self.slack_element_type_map.get('DEFAULT')
        return super().to_slack_modal_form(regex_field_filters=regex_field_filters,
                                           field_names_remap=field_names_remap,
                                           field_sort_order=field_sort_order,
                                           slack_element_type_map=slack_element_type_map,
                                           slack_element_options=slack_element_options)


@xray.capture()
class Towers(SLaterList):
    @xray.capture()
    def __init__(self, towers: list, limit: int = -1, next_token: int = -1) -> None:
        super().__init__(towers, limit=limit, next_token=next_token)

    @xray.capture()
    def to_external_select(self) -> Tuple:
        select = {
            'options': []
        }
        for tower in self.data:
            select.get('options').append(
                tower.to_external_select_option())
        return select

    @xray.capture()
    def to_json(self):
        """ Test that json loads into an object properly
        >>> towers = Towers([Tower(**{'tower_name': 'Eiffel Tower', 'tower_id':'1', 'next_link': 'https://localhost/towers?tower_id=1', 'date_last_update': '1970-01-01','contact_id':'1'})])

        >>> data = json.loads(towers.to_json())

        >>> data['towers'][0]['tower_name']
        'Eiffel Tower'
        """
        return super().to_json(type_name='towers')

    @xray.capture()
    def to_csv(self):
        """ Test that csv data loads into a pandas dataframe
        >>> towers = Towers([Tower(**{'tower_name': 'Eiffel Tower', 'tower_id':'1', 'next_link': 'https://localhost/towers?tower_id=1', 'date_last_update': '1970-01-01','contact_id':'1'})])
        >>> data = towers.to_csv().lstrip().splitlines()
        >>> assert (len(data) == 2), f'expected two rows (header and one data record), got {len(data)} rows'
        """
        return super().to_csv()
