const { Controller } = require('libflitter')

class RadiusController extends Controller {
    static get services() {
        return [...super.services, 'models', 'output']
    }

    async attempt(req, res, next) {
        const User = this.models.get('auth:User')
        const Client = this.models.get('radius:Client')

        if ( !req.body.username || !req.body.password ) {
            this.output.error('RADIUS error: missing username or password')
            return this.fail(res)
        }

        const parts = String(req.body.username).split('@')
        parts.reverse()

        const clientId = parts.shift()
        parts.reverse()

        const username = parts.join('@')
        const password = req.body.password

        const user = await User.findOne({ uid: username, active: true })
        if ( !user ) {
            this.output.error(`RADIUS error: invalid username: ${username}`)
            return this.fail(res)
        }

        const client = await Client.findById(clientId)
        if ( !client || !client.active ) {
            this.output.error(`RADIUS error: invalid client: ${clientId}`)
            return this.fail(res)
        }

        // Check if the credentials are an app_password
        const app_password_verified = Array.isArray(user.app_passwords)
            && user.app_passwords.length > 0
            && await user.check_app_password(password)

        // Check if the user has MFA enabled.
        // If so, split the incoming password to fetch the MFA code
        // e.g. normalPassword:123456
        if ( !app_password_verified && user.mfa_enabled ) {
            const parts = password.split(':')
            const mfa_code = parts.pop()
            const actual_password = parts.join(':')

            // Check the credentials
            if ( !(await user.check_password(actual_password)) ) {
                this.output.debug(`RADIUS error: user w/ MFA provided invalid credentials`)
                return this.fail(res)
            }

            // Now, check the MFA code
            if ( !user.mfa_token.verify(mfa_code) ) {
                this.output.debug(`RADIUS error: user w/ MFA provided invalid MFA token`)
                return this.fail(res)
            }

        // If not MFA, just check the credentials
        } else if (!app_password_verified && !await user.check_password(password)) {
            this.output.debug(`RADIUS error: user w/ simple auth provided invalid credentials`)
            return this.fail(res)
        }

        // Check if the user has any login interrupt traps set
        if ( user.trap ) {
            this.output.error(`RADIUS error: user has trap: ${user.trap}`)
            return this.fail(res)
        }

        // Apply the appropriate IAM policy if this SAML SP is associated with an App
        // If the SAML service provider has no associated application, just allow it
        const associated_app = await client.application()
        if ( associated_app ) {
            const Policy = this.models.get('iam:Policy')
            const can_access = await Policy.check_user_access(user, associated_app.id)
            if ( !can_access ) {
                this.output.error(`RADIUS error: user denied IAM access`)
                return this.fail(res)
            }
        }

        this.output.info(`Authenticated RADIUS user: ${user.uid} to IAM ${associated_app.name}`)
        return res.api({ success: true })
    }

    fail(res) {
        return res.status(401).api({ success: false })
    }

    async get_clients(req, res, next) {
        const Client = this.models.get('radius:Client')
        const clients = await Client.find({ active: true })
        const data = []

        for ( const client of clients ) {
            if ( req.user.can(`radius:client:${client.id}:view`) ) {
                data.push(await client.to_api())
            }
        }

        return res.api(data)
    }

    async get_client(req, res, next) {
        const Client = this.models.get('radius:Client')
        const client = await Client.findById(req.params.id)

        if ( !client || !client.active )
            return res.status(404)
                .message(req.T('api.client_not_found'))
                .api()

        if ( !req.user.can(`radius:client:${client.id}:view`) )
            return res.status(401)
                .message(req.T('api.insufficient_permissions'))
                .api()

        return res.api(await client.to_api())
    }

    async create_client(req, res, next) {
        if ( !req.user.can('radius:client:create') )
            return res.status(401)
                .message(req.T('api.insufficient_permissions'))
                .api()

        if ( !req.body.name )
            return res.status(400)
                .message(`${req.T('api.missing_field')} name`)
                .api()

        const Client = this.models.get('radius:Client')
        const client = new Client({
            name: req.body.name,
        })

        await client.save()
        return res.api(await client.to_api())
    }

    async update_client(req, res, next) {
        const Client = this.models.get('radius:Client')
        const client = await Client.findById(req.params.id)

        if ( !client || !client.active )
            return res.status(404)
                .message(req.T('api.client_not_found'))
                .api()

        if ( !req.user.can(`radius:client:${client.id}:update`) )
            return res.status(401)
                .message(req.T('api.insufficient_permissions'))
                .api()

        if ( !req.body.name )
            return res.status(400)
                .message(`${req.T('api.missing_field')} name`)
                .api()

        client.name = req.body.name
        await client.save()
        return res.api()
    }

    async delete_client(req, res, next) {
        const Client = this.models.get('radius:Client')
        const client = await Client.findById(req.params.id)

        if ( !client || !client.active )
            return res.status(404)
                .message(req.T('api.client_not_found'))
                .api()

        if ( !req.user.can(`radius:client:${client.id}:delete`) )
            return res.status(401)
                .message(req.T('api.insufficient_permissions'))
                .api()

        client.active = false
        await client.save()
        return res.api()
    }
}

module.exports = exports = RadiusController
