def __init__(self):
     print('Remedy email processor v' + __version__)
     self.outlook = win32com.client.Dispatch('Outlook.Application')
     self.inbox = None
     self.sdplus_api = None
     self.sdplus_api_key = os.environ['SDPLUS_ADMIN']
     self.sdplus_api_url = 'http://sdplus/sdpapi/request/'
     self.sdplus_clean = r'(?:##)(\d{6})(?:##)'  # clean sdplus number is group 1 as group(0)=entire match
     self.sdplus_csc = r'(?:NBNT|NBNTSD)(\d{6})'  # clean sdplus number is group 1
     self.hd_ref = r'(?:HD0*)(\d{7}\b)'  # clean 7 digit HD number is group 1 (match HD, 0 or infinite zeros, ref)
     self.service_desk_to = '*****@*****.**'
     self.destination_folder_name = 'Processed'
     self.slack = SlackAPI()
class OutlookSDPlus:
    """
    Process outlook folder for manage engine helpdesk number, confirm number is valid, forward email into Manage Engine
    appending the local reference number with ##s
    """
    def __init__(self):
        print('Remedy email processor v' + __version__)
        self.outlook = win32com.client.Dispatch('Outlook.Application')
        self.inbox = None
        self.sdplus_api = None
        self.sdplus_api_key = os.environ['SDPLUS_ADMIN']
        self.sdplus_api_url = 'http://sdplus/sdpapi/request/'
        self.sdplus_clean = r'(?:##)(\d{6})(?:##)'  # clean sdplus number is group 1 as group(0)=entire match
        self.sdplus_csc = r'(?:NBNT|NBNTSD)(\d{6})'  # clean sdplus number is group 1
        self.hd_ref = r'(?:HD0*)(\d{7}\b)'  # clean 7 digit HD number is group 1 (match HD, 0 or infinite zeros, ref)
        self.service_desk_to = '*****@*****.**'
        self.destination_folder_name = 'Processed'
        self.slack = SlackAPI()

    def process_emails(self):
        """
        Process outlook folder for badly-formed sdplus emails, edit subject and resend to help desk
        :return: None - Forwards emails appending ##131234## in subject and moves to a folder
        """
        mapi = self.outlook.GetNamespace('MAPI')
        # inbox = mapi.GetDefaultFolder(6)  # 6=olFolderInbox=my own inbox
        recipient = mapi.CreateRecipient('RM1048')  # 'Alias' of IT Third Party Response
        recipient.Resolve()
        if recipient.Resolved:
            # https://msdn.microsoft.com/en-us/library/office/ff869575.aspx
            self.inbox = mapi.GetSharedDefaultFolder(recipient, 6)
            messages = self.inbox.Items
            self.sdplus_api = API(self.sdplus_api_key, self.sdplus_api_url)
            print('Found ' + str(len(messages)) + ' messages to process:')
            # mailItem.Move changes inbox.Items.Count on a normal loop, but working from count to 0 works well
            for no in range(messages.Count-1, -1, -1):
                message = messages[no]
                hd = self.hd_ref_from_email(message)
                # sdplus clean, subject
                if re.search(self.sdplus_clean, message.Subject):
                    sdplus_found_number = re.search(self.sdplus_clean, message.Subject).group(1)
                    print(sdplus_found_number + ': sdplus clean, subject')
                    if self.sdplus_valid(sdplus_found_number):
                        self.update_sdplus(sdplus_found_number, 'Supplier Ref', hd)
                        self.slack_warn_if_not_assigned(sdplus_found_number)
                        self.send_move(message)
                # sdplus, subject
                elif re.search(self.sdplus_csc, message.Subject):
                    sdplus_found_number = re.search(self.sdplus_csc, message.Subject).group(1)
                    print(sdplus_found_number + ': sdplus, subject')
                    if self.sdplus_valid(sdplus_found_number):
                        self.update_sdplus(sdplus_found_number, 'Supplier Ref', hd)
                        self.slack_warn_if_not_assigned(sdplus_found_number)
                        self.send_move(message, ' ##' + sdplus_found_number + '##')
                # sdplus, body
                elif re.search(self.sdplus_csc, message.Body):
                    sdplus_found_number = re.search(self.sdplus_csc, message.Body).group(1)
                    print(sdplus_found_number + ': sdplus, body')
                    if self.sdplus_valid(sdplus_found_number):
                        self.update_sdplus(sdplus_found_number, 'Supplier Ref', hd)
                        self.slack_warn_if_not_assigned(sdplus_found_number)
                        self.send_move(message, ' ##' + sdplus_found_number + '##')
                # sdplus, body, remove_no #s
                elif re.search(self.sdplus_csc, message.Body.replace('#', '')):
                    sdplus_found_number = re.search(self.sdplus_csc, message.Body.replace('#', '')).group(1)
                    print(sdplus_found_number + ': sdplus, body, remove_no #s')
                    if self.sdplus_valid(sdplus_found_number):
                        self.update_sdplus(sdplus_found_number, 'Supplier Ref', hd)
                        self.slack_warn_if_not_assigned(sdplus_found_number)
                        self.send_move(message, ' ##' + sdplus_found_number + '##')
                else:
                    print("Can't work out sdplus number")

    def sdplus_valid(self, sdplus_ref):
        result = self.sdplus_api.send(sdplus_ref, 'GET_REQUEST')
        if result['response_status'] == 'Success':
            return True
        else:
            print('SDPlus number found (' + sdplus_ref + ') is not valid.')
            return False

    def hd_ref_from_email(self, message):
        if re.search(self.hd_ref, message.Subject):
            return re.search(self.hd_ref, message.Subject).group(1)
        if re.search(self.hd_ref, message.Body):
            return re.search(self.hd_ref, message.Body).group(1)

    def update_sdplus(self, sdplus_ref, field, value):
        call_current_values = self.sdplus_api.send(sdplus_ref, 'GET_REQUEST')
        if field in call_current_values or field.lower() in call_current_values:
            response = self.sdplus_api.send(sdplus_ref, 'EDIT_REQUEST', {field: value})
            if response['response_status'] == 'Success':
                return True
            else:
                return False

    def _is_assigned(self, sdplus):
        # Check if sdplus call has an Assignee
        call_details = self.sdplus_api.send(sdplus, 'GET_REQUEST')
        call_details = dict((k.lower(), v) for k, v in call_details.items())
        if call_details['technician']:
            return True
        else:
            return False

    def slack_warn_if_not_assigned(self, sdplus_ref):
        if not self._is_assigned(sdplus_ref):
            sdplus_href = '<http://sdplus/WorkOrder.do?woMode=viewWO&woID={sdplus_ref}|{sdplus_ref}>'\
                .format(sdplus_ref=sdplus_ref)
            # Send message to backoffice group, with @mitch, @simon, @paul
            self.slack.send('G1FBB4L68', 'Hey <@U1FBYK4BZ>, <@U1F4X362D>, <@U1FA6DMFV> - CSC have responded '
                                         'to SDPlus {0}, but this is currently unassigned...'.format(sdplus_href))

    def send_move(self, mail_item, append_to_subject=''):
        new_mail = mail_item.Forward()
        new_mail.To = self.service_desk_to
        if append_to_subject:
            new_mail.Subject += append_to_subject
        self.remove_signature(new_mail)
        self.insert_line_at_top(new_mail, 'To respond to CSC, please use: ', '*****@*****.**')
        print('Sending.')
        new_mail.Send()
        print('Moving.')
        mail_item.Move(self.inbox.Folders(self.destination_folder_name))
        print('Processed mail successfully.')

    @staticmethod
    def remove_signature(mail_object):
        """
        Most not possible without this link:
        https://msdn.microsoft.com/en-us/library/dd492012(v=office.12).aspx
        :param mail_object: mailItem object
        :return: (removes signature)
        """
        # Remove my default signature
        # https://msdn.microsoft.com/en-us/library/dd492012(v=office.12).aspx
        print('Attempting to remove_no signature.')
        if mail_object.BodyFormat == 1:  # Plain Text:
            find_text = '-----Original Message-----'
            mail_object.Body = mail_object.Body[mail_object.Body.find(find_text) + len(find_text):]
        if mail_object.BodyFormat == 2 or mail_object.BodyFormat == 3:  # HTML or RTF
            try:
                active_inspector = mail_object.GetInspector
                word_doc = active_inspector.WordEditor
                word_bookmark = word_doc.Bookmarks('_MailAutoSig')
                if word_bookmark:
                    word_bookmark.Select()
                    word_doc.Windows(1).Selection.Delete()  # (HTMLBody property only updated after Display() is called)
                    # assumes you will later call active_inspector.Close(0) (necessary if not calling .Display())
                    print('Signature removal success.')
            except pywintypes.com_error:
                print('Failed to remove_no signature.')
        return

    @staticmethod
    def insert_line_at_top(mail_obj, line, email):
        """
        Most not possible without this link:
        https://msdn.microsoft.com/en-us/library/dd492012(v=office.12).aspx
        :param mail_obj: mailItem object
        :param line: Line to put in top of email
        :param email: Email to immediate right of line
        :return: (updates mailItem object)
        """
        if mail_obj.BodyFormat == 1:  # Plain text
            mail_obj.Body = line + email + '\n' + mail_obj.Body
        elif mail_obj.BodyFormat == 2 or mail_obj.BodyFormat == 3:  # HTML or RTF
            active_inspector = mail_obj.GetInspector
            word_doc = active_inspector.WordEditor
            word_selection = word_doc.Windows(1).Selection
            word_selection.Move(6, -1)  # 6=wdStory. Move to beginning
            word_doc.Characters(1).InsertBefore(line)
            word_selection.Move(5, 1)  # 5=wdLine. Move down 1 line
            word_selection.Move(1, -1)  # 1=wdCharacter. Move back 1
            word_doc.Hyperlinks.Add(word_selection.Range, 'mailto:' + email, '', '', email, '')
            active_inspector.Close(0)  # olSave = 0, olDiscard = 1, olPromptForSave = 2