const LDAPController = require('./LDAPController')
const LDAP = require('ldapjs')

class SudoController extends LDAPController {
    static get services() {
        return [
            ...super.services,
            'output',
            'ldap_server',
            'models',
            'configs',
            'auth'
        ]
    }

    constructor() {
        super()
        this.Group = this.models.get('auth:Group')
        this.User = this.models.get('auth:User')
    }

    // TODO flitter-orm chunk query
    // TODO generalize scoped search logic
    async search_sudo(req, res, next) {
        if ( !req.user.can('ldap:search:sudo') ) {
            return next(new LDAP.InsufficientAccessRightsError())
        }

        const sudo_hosts = this.parse_sudo_hosts(req.filter)
        const iam_targets = await this.get_targets_from_hosts(sudo_hosts)

        if ( req.scope === 'base' ) {
            // If scope is base, check if the base DN matches the filter.
            // If so, return it. Else, return empty.
            this.output.debug(`Running base DN search for sudo with DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)

            const user = await this.get_resource_from_dn(req.dn)

            // Make sure the user is ldap visible && match the filter
            if ( user && user.ldap_visible && req.filter.matches(await user.to_sudo(iam_targets)) ) {

                // If so, send the object
                res.send({
                    dn: user.sudo_dn.format(this.configs.get('ldap:server.format')),
                    attributes: await user.to_sudo(iam_targets),
                })
            }
        } else if ( req.scope === 'one' ) {
            // If scope is one, find all entries that are the immediate
            // subordinates of the base DN that match the filter.
            this.output.debug(`Running one DN search for sudo with DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)

            // Fetch the LDAP-visible users
            const users = await this.Group.sudo_directory()
            for ( const user of users ) {

                // Make sure the user os of the appropriate scope
                if ( req.dn.equals(user.sudo_dn) || user.sudo_dn.parent().equals(req.dn) ) {

                    // Check if the filter matches
                    if ( req.filter.matches(await user.to_sudo(iam_targets)) ) {

                        // If so, send the object
                        res.send({
                            dn: user.sudo_dn.format(this.configs.get('ldap:server.format')),
                            attributes: await user.to_sudo(iam_targets),
                        })
                    }
                }
            }

        } else if ( req.scope === 'sub' ) {
            // If scope is sub, find all entries that are subordinates
            // of the base DN at any level and match the filter.
            this.output.debug(`Running sub DN search for sudo with DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)

            // Fetch the users as LDAP objects
            const users = await this.Group.sudo_directory()
            for ( const user of users ) {

                // Make sure the user is of appropriate scope
                if ( req.dn.equals(user.sudo_dn) || req.dn.parentOf(user.sudo_dn) ) {

                    // Check if filter matches
                    if ( req.filter.matches(await user.to_sudo(iam_targets)) ) {

                        // If so, send the object
                        res.send({
                            dn: user.sudo_dn.format(this.configs.get('ldap:server.format')),
                            attributes: await user.to_sudo(iam_targets),
                        })
                    }
                }
            }
        } else {
            this.output.error(`Attempted to perform LDAP search with invalid scope: ${req.scope}`)
            return next(new LDAP.OtherError('Attempted to perform LDAP search with invalid scope.'))
        }

        res.end()
        return next()
    }

    parse_sudo_hosts(filter, target_hosts = []) {
        if ( Array.isArray(filter?.filters) ) {
            for ( const sub_filter of filter.filters ) {
                target_hosts = [...target_hosts, ...this.parse_sudo_hosts(sub_filter)]
            }
        } else if ( filter?.attribute ) {
            if ( filter.attribute === 'sudohost' ) {
                target_hosts.push(filter.value)
            }
        }

        return target_hosts.filter(Boolean)
    }

    async get_targets_from_hosts(sudo_hosts) {
        const Machine = this.models.get('ldap:Machine')
        const machines = await Machine.find({
            active: true,
            ldap_visible: true,
            host_name: {
                $in: sudo_hosts.filter(x => x.toLowerCase() !== 'all' && x.indexOf('*') < 0),
            }
        })

        return machines.map(x => x.id)
    }

    get_cn_from_dn(dn) {
        try {
            if ( typeof dn === 'string' ) dn = LDAP.parseDN(dn)
            return dn.rdns[0].attrs.cn.value
        } catch (e) { console.log('Error parsing CN from DN', e) }
    }

    async get_resource_from_dn(sudo_dn) {
        const cn = this.get_cn_from_dn(sudo_dn)
        if ( cn ) {
            return this.User.findOne({uid: cn.substr(5), ldap_visible: true})
        }
    }
}

module.exports = exports = SudoController
