const { Model } = require('flitter-orm')

// TODO - remove specific :create checks; auto-grant permissions on create

class PolicyModel extends Model {
    static get services() {
        return [...super.services, 'models', 'canon']
    }

    static get schema() {
        return {
            entity_type: String, // user | group
            entity_id: String,
            access_type: String, // allow | deny
            target_type: { type: String, default: 'application' }, // application | api_scope | machine | machine_group | vault
            target_id: String,
            active: { type: Boolean, default: true },
            for_permission: { type: Boolean, default: false },
            permission: String,
        }
    }

    static async check_allow(entity_id, target_id, permission = undefined) {
        const policies = await this.find({
            entity_id,
            target_id,
            access_type: 'allow',
            active: true,
            ...(permission ? {
                for_permission: true,
                permission,
            } : {})
        })

        return policies.length > 0
    }

    static async check_deny(entity_id, target_id, permission = undefined) {
        const policies = await this.find({
            entity_id,
            target_id,
            access_type: 'deny',
            active: true,
            ...(permission ? {
                for_permission: true,
                permission,
            } : {})
        })

        return policies.length === 0
    }

    static async check_entity_access(entity_id, target_id, permission = undefined) {
        return (await this.check_allow(entity_id, target_id, permission)) && !(await this.check_deny(entity_id, target_id, permission))
    }

    static async check_user_denied(user, target_id, permission = undefined) {
        const groups = await user.groups()
        const group_ids = groups.map(x => x.id)

        const user_denials = await this.find({
            entity_id: user.id,
            target_id,
            access_type: 'deny',
            active: true,
            ...(permission ? {
                for_permission: true,
                permission,
            } : {})
        })

        const group_denials = await this.find({
            entity_id: { $in: group_ids },
            target_id,
            access_type: 'deny',
            active: true,
            ...(permission ? {
                for_permission: true,
                permission,
            } : {})
        })

        return user_denials.length > 0 || group_denials.length > 0
    }

    static async get_all_related(target_id) {
        const all = [target_id]
        const Machine = this.prototype.models.get('ldap:Machine')
        const MachineGroup = this.prototype.models.get('ldap:MachineGroup')

        const machine = await Machine.findById(target_id)
        if ( machine?.active ) {
            const groups = await MachineGroup.find({
                active: true,
                machine_ids: machine.id,
            })

            groups.map(x => all.push(x.id))
        }

        const group = await MachineGroup.findById(target_id)
        if ( group?.active ) {
            const machines = await Machine.find({
                active: true,
                _id: {
                    $in: group.machine_ids.map(x => Machine.to_object_id(x)),
                }
            })

            machines.map(x => all.push(x.id))
        }

        return all
    }

    static async check_user_access(user, target_id, permission = undefined) {
        const groups = await user.groups()
        const group_ids = groups.map(x => x.id)
        const target_ids = await this.get_all_related(target_id)

        const user_approvals = await this.find({
            entity_id: user.id,
            target_id: { $in: target_ids },
            access_type: 'allow',
            active: true,
            ...(permission ? {
                for_permission: true,
                permission,
            } : {})
        })

        const user_denials = await this.find({
            entity_id: user.id,
            target_id: { $in: target_ids },
            access_type: 'deny',
            active: true,
            ...(permission ? {
                for_permission: true,
                permission,
            } : {})
        })

        const group_approvals = await this.find({
            entity_id: { $in: group_ids },
            target_id: { $in: target_ids },
            access_type: 'allow',
            active: true,
            ...(permission ? {
                for_permission: true,
                permission,
            } : {})
        })

        const group_denials = await this.find({
            entity_id: { $in: group_ids },
            target_id: { $in: target_ids },
            access_type: 'deny',
            active: true,
            ...(permission ? {
                for_permission: true,
                permission,
            } : {})
        })

        // IF user has explicit denial, deny
        if ( user_denials.length > 0 ) return false

        // ELSE IF user has explicit approval, approve
        if ( user_approvals.length > 0 ) return true

        // ELSE IF group has denial, deny
        if ( group_denials.length > 0 ) return false

        // ELSE IF group has approval, approve
        if ( group_approvals.length > 0 ) return true

        // ELSE deny
        return false
    }

    async to_api() {
        let entity_display = ''
        if ( this.entity_type === 'user' ) {
            const User = this.models.get('auth:User')
            const user = await User.findById(this.entity_id)
            entity_display = `User: ${user.last_name}, ${user.first_name} (${user.uid.toLowerCase()})`
        } else if ( this.entity_type === 'group' ) {
            const Group = this.models.get('auth:Group')
            const group = await Group.findById(this.entity_id)
            entity_display = `Group: ${group.name} (${group.user_ids.length} users)`
        }

        let target_display = ''
        if ( this.target_type === 'application' ) {
            const Application = this.models.get('Application')
            const app = await Application.findById(this.target_id)
            target_display = `Application: ${app.name}`
        } else if ( this.target_type === 'api_scope' ) {
            target_display = `API Scope: ${this.target_id}`
        } else if ( this.target_type === 'machine' ) {
            const Machine = this.models.get('ldap:Machine')
            const machine = await Machine.findById(this.target_id)
            target_display = `Computer: ${machine.name}`

            if ( machine.host_name ) {
                target_display += ` (${machine.host_name})`
            }
        } else if ( this.target_type === 'machine_group' ) {
            const MachineGroup = this.models.get('ldap:MachineGroup')
            const group = await MachineGroup.findById(this.target_id)
            target_display = `Computer Group: ${group.name} (${group.machine_ids.length} computers)`
        } else if ( this.target_type === 'vault' ) {
            const Vault = this.models.get('vault:Vault')
            const vault = await Vault.findById(this.target_id)
            target_display = `Vault: ${vault.name}`
        }

        return {
            id: this.id,
            entity_display,
            entity_type: this.entity_type,
            entity_id: this.entity_id,
            access_type: this.access_type,
            target_display,
            target_type: this.target_type,
            target_id: this.target_id,
            for_permission: this.for_permission,
            permission: this.permission,
        }
    }
}

module.exports = exports = PolicyModel
