import {Injectable} from "@nestjs/common";
import ldap, {Server, createServer, parseDN, InsufficientAccessRightsError} from "ldapjs";
import {Bind, equalsDN, getPersonMatchedDN, ldapRegister, ldapServer, Search} from "./function";
import {getBaseEntries, getOrganizationBaseDN, getRootDN, validateAdminPassword} from "../utils/ldap";
import {UserService} from "../user/user.service";

@Injectable()
export class LdapService {
    public readonly server: Server = ldapServer;

    constructor(private userService: UserService) {

    }

    public run(port: number) {
        ldapRegister(this);
        this.server.listen(port);
    }

    @Search('')
    private searchDSE(req, res, next) {
        const baseObject = {
            dn: '',
            structuralObjectClass: 'OpenLDAProotDSE',
            configContext: 'cn=config',
            attributes: {
                objectclass: ['top', 'OpenLDAProotDSE'],
                namingContexts: [getRootDN()],
                supportedLDAPVersion: ['3'],
                subschemaSubentry: ['cn=Subschema']
            }
        };
        console.info("scope " + req.scope + " filter " + req.filter + " baseObject " + req.baseObject);
        if ('base' == req.scope
            && '(objectclass=*)' == req.filter.toString()
            && req.baseObject == '') {
            res.send(baseObject);
        }

        //log.info('scope: ' + req.scope);
        //log.info('filter: ' + req.filter.toString());
        //log.info('attributes: ' + req.attributes);
        res.end();
        return next();
    }

    @Search('cn=Subschema')
    private searchSchema(req, res, next) {
        var schema = {
            dn: 'cn=Subschema',
            attributes: {
                objectclass: ['top', 'subentry', 'subschema', 'extensibleObject'],
                cn: ['Subschema']
            }
        };
        res.send(schema);
        res.end();
        return next();
    }

    @Search(getRootDN())
    private async search(req, res, next) {
        console.info("search req scope " + req.scope + " filter " + req.filter + " baseObject " + req.baseObject);
        // console.log('sizeLimit', req.sizeLimit, 'timeLimit', req.timeLimit);
        const reqDN = parseDN(req.baseObject.toString().toLowerCase());

        const comparators = {
            'one': (objDN) => {
                return equalsDN(reqDN, objDN.parent());
            },
            'sub': (objDN) => {
                // console.log(reqDN.toString())
                // console.log(objDN.toString())
                return equalsDN(reqDN, objDN) || reqDN.parentOf(objDN);
            },
            'base': (objDN) => {
                return equalsDN(reqDN, objDN);
            },
        }


        const comparator = comparators[req.scope];
        if (comparator) {
            const entries = await this.userService.fetchAllUserEntries();
            [
                ...getBaseEntries(),
                ...entries,
                // ...getLDAPCustomGroupEntries(),
            ].filter(entry => {
                // console.log(entry.dn)
                return comparator(parseDN(entry.dn.toLowerCase())) && req.filter.matches(entry.attributes);
            }).forEach(entry => {
                console.debug('send entry', entry.dn);
                res.send(entry);
            });
        }

        res.end();
        return next();
    }

    @Bind(getRootDN())
    async bind(req, res, next) {
        const reqDN = parseDN(req.dn.toString().toLowerCase());
        // console.debug('bind req', reqDN)
        // console.log('bind PW: ' + req.credentials);
        if (parseDN(getRootDN().toLowerCase()).equals(reqDN.parent())) {
            // admins
            const username = reqDN['rdns'][0].attrs.cn.value;
            if (validateAdminPassword(username, req.credentials)) {
                res.end();
                return next();
            } else {
                return next(new ldap.InvalidCredentialsError());
            }

        } else if (parseDN(getOrganizationBaseDN().toLowerCase()).parentOf(reqDN)) {
            // users
            const cn = reqDN['rdns'][0].attrs.cn.value;
            const matchedUser = await this.userService.findOne(cn)
            console.log(matchedUser)
            // if (matchedUser) {
            //     const record = await getDBRecordForUserId(matchedUser.attributes.uid);
            //     if (record && validateUserPassword(record.password, req.credentials)) {
            //         res.end();
            //         return next();
            //     } else {
            //         console.debug('password failed');
            //         return next(new ldap.InvalidCredentialsError());
            //     }
            // } else {
            //     console.debug('user not found');
            //     return next(new ldap.InvalidCredentialsError());
            // }
        }

        return next(new InsufficientAccessRightsError());
    }
}
