#!/usr/bin/env python
#
# The script fetches data from Bamboo and compares it with data in FreeIPA's LDAP directory.
# Any changes to Bamboo records are synced to LDAP directory.
# Any accounts that do not exist in LDAP are created as FreeIPA stage accounts.
#
# Author: Peter Pakos <peter.pakos@wandisco.com>

from __future__ import print_function

import os
import sys
import argparse
import datetime
import tzlocal

import CONFIG
import WDBamboo
import WDLdap
import WDMail

from unidecode import unidecode


class Main(object):
    _version = '17.5.22'
    _name = os.path.splitext(os.path.basename(os.path.realpath(__file__)))[0]

    def __init__(self):
        self._parser, self._args = self._parse_args()
        self._force_all = False
        if type(self._args.uid) is list:
            self._force_uid = self._args.uid
            if len(self._args.uid) == 0:
                self._force_all = True
        else:
            self._force_uid = []
        self._bamboo_url = CONFIG.CONFIG.BAMBOO_URL
        self._bamboo_api_key = CONFIG.CONFIG.BAMBOO_API_KEY
        self._ipa_server = CONFIG.CONFIG.IPA_SERVER
        self._bind_dn = CONFIG.CONFIG.BIND_DN
        self._bind_pw = CONFIG.CONFIG.BIND_PW
        self._bamboo_exclude_list = CONFIG.CONFIG.BAMBOO_EXCLUDE_LIST
        self._notification_to = CONFIG.CONFIG.NOTIFICATION_TO
        self._mail = WDMail.WDMail()

        if self._args.bamboo:
            self._bamboo = WDBamboo.WDBamboo(self._bamboo_url, self._bamboo_api_key)
            self._bamboo.display_data()
        if self._args.ldap:
            self._ldap = WDLdap.WDLdap(self._ipa_server, self._bind_dn, self._bind_pw)
            self._ldap.display_data()
        if self._args.bamboo or self._args.ldap:
            exit()
        if self._args.sync:
            self._bamboo = WDBamboo.WDBamboo(self._bamboo_url, self._bamboo_api_key)
            self._ldap = WDLdap.WDLdap(self._ipa_server, self._bind_dn, self._bind_pw)
            self.sync_data()
            exit()
        self._parser.print_help()

    def _parse_args(self):
        parser = argparse.ArgumentParser(description='A tool to synchronise FreeIPA with Bamboo HR')
        parser.add_argument('--version', action='version', version='%s %s' % (self._name, self._version))
        parser.add_argument('-l', '--ldap', help='print LDAP data and exit', dest='ldap', action='store_true')
        parser.add_argument('-b', '--bamboo', help='print Bamboo data and exit', dest='bamboo', action='store_true')
        parser.add_argument('-s', '--sync', help='synchronise LDAP with Bamboo', dest='sync', action='store_true')
        parser.add_argument('-n', '--notification', help='send New Starter Notification (requires -s)', dest='notify',
                            action='store_true')
        parser.add_argument('-f', '--force', help='force changes for given UIDs (or all if none provided)', dest='uid',
                            nargs='*', action='store')
        parser.add_argument('-N', '--noop', help='dry-run mode', dest='noop',
                            action='store_true')
        return parser, parser.parse_args()

    def sync_data(self):
        directory = self._bamboo.get_directory()
        local_tz = tzlocal.get_localzone()
        now = local_tz.localize(datetime.datetime.now()).date()
        for bamboo_id, bamboo_fields in directory.items():
            if bamboo_fields['workEmail'] is not None and bamboo_fields['workEmail'] not in self._bamboo_exclude_list:
                result = self._ldap.mail_exists(bamboo_fields['workEmail'])
                printed = False
                if len(result) == 0:
                    bamboo_uid = str(unidecode(bamboo_fields['firstName'].decode('utf8')) + '.' +
                                     unidecode(bamboo_fields['lastName'].decode('utf8'))).lower()
                    mail_uid = bamboo_fields['workEmail'].partition('@')[0]
                    fields = self._bamboo.fetch_field(bamboo_id, [
                            'hireDate',
                            'terminationDate',
                            'homeEmail',
                            'homePhone',
                            'supervisor',
                            'supervisorEid',
                            'customonboardingNotes',
                            'customrequestedPhone',
                            'customrequestedLaptop',
                            'customrequestedMonitor',
                            'location',
                            'customTeams',
                            'customSystems'
                        ])

                    if self._ldap.user_exists(bamboo_uid):
                        exists = 'Active'
                        exists_uid = bamboo_uid
                    elif self._ldap.user_exists(bamboo_uid, 'stage'):
                        exists = 'Stage'
                        exists_uid = bamboo_uid
                    elif self._ldap.user_exists(bamboo_uid, 'preserved'):
                        exists = 'Preserved'
                        exists_uid = bamboo_uid
                    else:
                        exists = False
                        exists_uid = None

                    if not exists and bamboo_uid != mail_uid:
                        if self._ldap.user_exists(mail_uid):
                            exists = 'Active'
                            exists_uid = mail_uid
                        elif self._ldap.user_exists(mail_uid, 'stage'):
                            exists = 'Stage'
                            exists_uid = mail_uid
                        elif self._ldap.user_exists(mail_uid, 'preserved'):
                            exists = 'Preserved'
                            exists_uid = mail_uid

                    if fields['hireDate'] and fields['hireDate'] != '0000-00-00' and exists == 'Stage' \
                            and not self._force_all and bamboo_uid not in self._force_uid and not self._args.noop:
                        hire_date = local_tz.localize(datetime.datetime.strptime(fields['hireDate'], '%Y-%m-%d')).date()
                        if hire_date > now:
                            continue

                    print('New Bamboo account: %s %s (%s)' % (bamboo_fields['firstName'], bamboo_fields['lastName'],
                                                              bamboo_fields['workEmail']))
                    print('- Job Title: %s' % bamboo_fields['jobTitle'])
                    print('- Department: %s' % bamboo_fields['department'])
                    print('- Location: %s' % fields['location'])
                    print('- Manager: %s' % fields['supervisor'])
                    print('- Start date: %s' % fields['hireDate'])
                    printed = True

                    if exists:
                        print('%s LDAP account %s already exists\n' % (exists, exists_uid))
                        continue

                    if fields['terminationDate'] and fields['terminationDate'] != '0000-00-00':
                            print('User leaving on %s, skipping account creation\n' % fields['terminationDate'])
                            continue

                    if fields['hireDate'] and fields['hireDate'] != '0000-00-00' and not self._force_all \
                            and bamboo_uid not in self._force_uid:
                        hire_date = local_tz.localize(datetime.datetime.strptime(fields['hireDate'], '%Y-%m-%d')).date()
                        if hire_date < now:
                            print('Start date is in the past, skipping account creation (use -f to force)\n')
                            continue

                    if not exists:
                        if fields['supervisorEid']:
                            supervisor_email = self._bamboo.fetch_field(fields['supervisorEid'], ['workEmail'])
                        else:
                            supervisor_email = None
                        print('Creating stage LDAP account %s: ' % bamboo_uid, end='')
                        if self._args.noop:
                            print('DRY-RUN')
                            user_created = False
                        else:
                            if self._ldap.add_user(
                                bamboo_uid,
                                bamboo_id,
                                bamboo_fields['firstName'],
                                bamboo_fields['lastName'],
                                bamboo_fields['department'],
                                bamboo_fields['jobTitle'],
                                bamboo_fields['mobilePhone'],
                                bamboo_fields['workEmail'],
                                bamboo_fields['division']
                            ):
                                print('OK')
                                user_created = True
                            else:
                                print('FAIL')
                                user_created = False

                        print('Sending New Starter Notification: ', end='')
                        if not self._args.notify or self._args.noop:
                            print('NO\n')
                            continue
                        message = '''*Personal Information*
Name: %s %s
Job Title: %s
Home Phone: %s
Home Email: %s
Start Date: %s

*Department Information*
Department: %s
Location: %s
Division: %s
Manager Name: %s
Manager Email: %s

*Requirements*
Phone: %s
Laptop: %s
Monitor: %s
Teams: %s
Systems: %s

*Onboarding Notes*
%s

*LDAP uid:* %s

*Stage LDAP user created:* %s
''' % (
                            bamboo_fields['firstName'],
                            bamboo_fields['lastName'],
                            bamboo_fields['jobTitle'],
                            fields['homePhone'],
                            fields['homeEmail'],
                            fields['hireDate'],
                            bamboo_fields['department'],
                            fields['location'],
                            bamboo_fields['division'],
                            fields['supervisor'],
                            supervisor_email,
                            fields['customrequestedPhone'],
                            fields['customrequestedLaptop'],
                            fields['customrequestedMonitor'],
                            fields['customTeams'],
                            fields['customSystems'],
                            fields['customonboardingNotes'],
                            bamboo_uid,
                            'Yes' if user_created else 'No'
                        )

                        if not supervisor_email:
                            supervisor_email = self._notification_to
                        if self._mail.send(
                                supervisor_email,
                                [self._notification_to],
                                'New Starter Notification: %s %s' % (bamboo_fields['firstName'],
                                                                     bamboo_fields['lastName']),
                                message,
                                cc=[supervisor_email],
                                html=True
                        ) == 202:
                            print('OK')
                        else:
                            print('FAIL')
                elif len(result) == 1:
                    for dn, attrs in result.items():
                        uid = attrs.get('uid')
                        mobile = attrs.get('mobile')
                        title = attrs.get('title')
                        eid = attrs.get('employeeNumber')
                        dept = attrs.get('departmentNumber')
                        division = attrs.get('ou')
                        phone = attrs.get('telephoneNumber')
                        if type(uid) is list:
                            uid = uid[0]
                        if type(mobile) is list:
                            mobile = mobile[0]
                        if type(title) is list:
                            title = title[0]
                        if type(eid) is list:
                            eid = eid[0]
                        if type(dept) is list:
                            dept = dept[0]
                        if type(division) is list:
                            division = division[0]
                        if type(phone) is list:
                            phone = phone[0]

                        if bamboo_fields['mobilePhone'] != mobile:
                            printed = True
                            print('%s: updating mobile from \'%s\' to \'%s\': '
                                  % (uid, mobile, bamboo_fields['mobilePhone']), end='')
                            if self._args.noop:
                                print('DRY-RUN')
                            else:
                                if self._ldap.modify(dn, 'mobile', mobile, bamboo_fields['mobilePhone']):
                                    print('OK')
                                else:
                                    print('FAIL')

                        if bamboo_fields['mobilePhone'] != phone:
                            printed = True
                            print('%s: updating telephoneNumber from \'%s\' to \'%s\': '
                                  % (uid, phone, bamboo_fields['mobilePhone']), end='')
                            if self._args.noop:
                                print('DRY-RUN')
                            else:
                                if self._ldap.modify(dn, 'telephoneNumber', phone, bamboo_fields['mobilePhone']):
                                    print('OK')
                                else:
                                    print('FAIL')

                        if bamboo_fields['jobTitle'] != title:
                            printed = True
                            print('%s: updating title from \'%s\' to \'%s\': '
                                  % (uid, title, bamboo_fields['jobTitle']), end='')
                            if self._args.noop:
                                print('DRY-RUN')
                            else:
                                if self._ldap.modify(dn, 'title', title, bamboo_fields['jobTitle']):
                                    print('OK')
                                else:
                                    print('FAIL')

                        if bamboo_id != eid:
                            printed = True
                            print('%s: updating employeeNumber from \'%s\' to \'%s\': '
                                  % (uid, eid, bamboo_id), end='')
                            if self._args.noop:
                                print('DRY-RUN')
                            else:
                                if self._ldap.modify(dn, 'employeeNumber', eid, bamboo_id):
                                    print('OK')
                                else:
                                    print('FAIL')

                        if bamboo_fields['department'] != dept:
                            printed = True
                            print('%s: updating departmentNumber from \'%s\' to \'%s\': '
                                  % (uid, dept, bamboo_fields['department']), end='')
                            if self._args.noop:
                                print('DRY-RUN')
                            else:
                                if self._ldap.modify(dn, 'departmentNumber', dept, bamboo_fields['department']):
                                    print('OK')
                                else:
                                    print('FAIL')

                        if bamboo_fields['division'] != division:
                            printed = True
                            print('%s: updating ou from \'%s\' to \'%s\': '
                                  % (uid, division, bamboo_fields['division']), end='')
                            if self._args.noop:
                                print('DRY-RUN')
                            else:
                                if self._ldap.modify(dn, 'ou', division, bamboo_fields['division']):
                                    print('OK')
                                else:
                                    print('FAIL')

                else:
                    printed = True
                    print('More than one DN found in LDAP with email %s' % bamboo_fields['workEmail'],
                          file=sys.stderr)
                if printed:
                    print()


if __name__ == '__main__':
    try:
        main = Main()
    except KeyboardInterrupt:
        print('\nCancelling...')
