const ldap = require('../util/ldapjs-extra');
const ssh = require('node-ssh');

/**
 * Summary of userAccountControl flags:
 * https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties
 */
const UserAccountControlFlags = {
    ACCOUNTDISABLE: 2
};

module.exports = Self => {
    Self.getSynchronizer = async function() {
        return await Self.findOne({
            fields: [
                'host',
                'adDomain',
                'adController',
                'adUser',
                'adPassword',
                'verifyCert'
            ]
        });
    };

    Object.assign(Self.prototype, {
        async init() {
            let sshClient = new ssh.NodeSSH();
            await sshClient.connect({
                host: this.adController,
                username: this.adUser,
                password: this.adPassword
            });

            let adUser = `cn=${this.adUser},${this.usersDn()}`;

            let adClient = ldap.createClient({
                url: `ldaps://${this.adController}:636`,
                tlsOptions: {rejectUnauthorized: this.verifyCert}
            });
            await adClient.bind(adUser, this.adPassword);

            Object.assign(this, {
                sshClient,
                adClient
            });
        },

        async deinit() {
            await this.sshClient.dispose();
            await this.adClient.unbind();
        },

        usersDn() {
            let dnBase = this.adDomain
                .split('.')
                .map(part => `dc=${part}`)
                .join(',');
            return `cn=Users,${dnBase}`;
        },

        async syncUser(userName, info, password) {
            let {sshClient} = this;

            let sambaUser = await this.adClient.searchOne(this.usersDn(), {
                scope: 'sub',
                attributes: ['userAccountControl'],
                filter: `(&(objectClass=user)(sAMAccountName=${userName}))`
            });
            let isEnabled = sambaUser
                && !(sambaUser.userAccountControl & UserAccountControlFlags.ACCOUNTDISABLE);

            if (info.hasAccount) {
                if (!sambaUser) {
                    await sshClient.exec('samba-tool user create', [
                        userName,
                        '--uid-number', `${info.uidNumber}`,
                        '--mail-address', info.corporateMail,
                        '--random-password'
                    ]);
                    await sshClient.exec('samba-tool user setexpiry', [
                        userName,
                        '--noexpiry'
                    ]);
                    await sshClient.exec('mkhomedir_helper', [
                        userName,
                        '0027'
                    ]);
                }
                if (!isEnabled) {
                    await sshClient.exec('samba-tool user enable', [
                        userName
                    ]);
                }
                if (password) {
                    await sshClient.exec('samba-tool user setpassword', [
                        userName,
                        '--newpassword', password
                    ]);
                }
            } else if (isEnabled) {
                await sshClient.exec('samba-tool user disable', [
                    userName
                ]);
                console.log(` -> User '${userName}' disabled on Samba`);
            }
        },

        /**
         * Gets Samba enabled users.
         *
         * @param {Set} usersToSync
         */
        async getUsers(usersToSync) {
            const LDAP_MATCHING_RULE_BIT_AND = '1.2.840.113556.1.4.803';
            let filter = `!(userAccountControl:${LDAP_MATCHING_RULE_BIT_AND}:=${UserAccountControlFlags.ACCOUNTDISABLE})`;

            let opts = {
                scope: 'sub',
                attributes: ['sAMAccountName'],
                filter: `(&(objectClass=user)(${filter}))`
            };
            await this.adClient.searchForeach(this.usersDn(), opts,
                o => usersToSync.add(o.sAMAccountName));
        }
    });
};