const { Controller } = require('libflitter')

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

    async get_applications(req, res, next) {
        const Application = this.models.get('Application')
        const applications = await Application.find({ active: true })
        const data = []

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

        return res.api(data)
    }

    async get_application(req, res, next) {
        const Application = this.models.get('Application')
        const application = await Application.findById(req.params.id)

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

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

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

    async create_application(req, res, next) {
        const Application = this.models.get('Application')

        if ( !req.user.can('app:create') )
            return res.status(401)
                .message(req.T('api.insufficient_permissions'))
                .api()

        const required_fields = ['name', 'identifier']
        for ( const field of required_fields ) {
            if ( !req.body[field] )
                return res.status(400)
                    .message(`${req.T('api.missing_field')} ${field}`)
                    .api()
        }

        // Make sure the identifier is properly formatted
        if ( !(new RegExp('^[a-zA-Z0-9_]*$')).test(req.body.identifier) )
            return res.status(400)
                .message(`${req.T('api.improper_field')} identifier ${req.T('api.alphanum_underscores')}`)
                .api()

        // Make sure the identifier is unique
        const existing_app = await Application.findOne({ identifier: req.body.identifier })
        if ( existing_app )
            return res.status(400)
                .message(req.T('api.application_already_exists'))
                .api()

        const application = new Application({
            name: req.body.name,
            identifier: req.body.identifier,
            description: req.body.description,
        })

        // Verify LDAP client IDs
        const LDAPClient = this.models.get('ldap:Client')
        if ( req.body.ldap_client_ids ) {
            const parsed = typeof req.body.ldap_client_ids === 'string' ? this.utility.infer(req.body.ldap_client_ids) : req.body.ldap_client_ids
            const ldap_client_ids = Array.isArray(parsed) ? parsed : [parsed]
            for ( const id of ldap_client_ids ) {
                const client = await LDAPClient.findById(id)
                if ( !client || !client.active || !req.user.can(`ldap:client:${client.id}:view`) )
                    return res.status(400)
                        .message(`${req.T('api.invalid_ldap_client_id')} ${id}`)
                        .api()

                const other_assoc_app = await Application.findOne({ ldap_client_ids: client.id })
                if ( other_assoc_app )
                    return res.status(400) // TODO translate this
                        .message(`The LDAP client ${client.name} is already associated with an existing application (${other_assoc_app.name}).`)
                        .api()
            }

            application.ldap_client_ids = ldap_client_ids
        }

        // Verify OAuth client IDs
        const OAuthClient = this.models.get('oauth:Client')
        if ( req.body.oauth_client_ids ) {
            const parsed = typeof req.body.oauth_client_ids === 'string' ? this.utility.infer(req.body.oauth_client_ids) : req.body.oauth_client_ids
            const oauth_client_ids = Array.isArray(parsed) ? parsed : [parsed]
            for ( const id of oauth_client_ids ) {
                const client = await OAuthClient.findById(id)
                if ( !client || !client.active || !req.user.can(`oauth:client:${client.id}:view`) )
                    return res.status(400)
                        .message(`${req.T('api.invalid_oauth_client_id')} ${id}`)
                        .api()

                const other_assoc_app = await Application.findOne({ oauth_client_ids: client.id })
                if ( other_assoc_app )
                    return res.status(400) // TODO translate this
                        .message(`The OAuth2 client ${client.name} is already associated with an existing application (${other_assoc_app.name}).`)
                        .api()
            }

            application.oauth_client_ids = oauth_client_ids
        }

        // Verify OpenID client IDs
        const OpenIDClient = this.models.get('openid:Client')
        if ( req.body.openid_client_ids ) {
            const parsed = typeof req.body.openid_client_ids === 'string' ? this.utility.infer(req.body.openid_client_ids) : req.body.openid_client_ids
            const openid_client_ids = Array.isArray(parsed) ? parsed : [parsed]
            for ( const id of openid_client_ids ) {
                const client = await OpenIDClient.findById(id)
                if ( !client )
                    return res.status(400)
                        .message(`${req.T('api.invalid_oauth_client_id')} ${id}`)
                        .api()

                const other_assoc_app = await Application.findOne({ openid_client_ids: client.id })
                if ( other_assoc_app )
                    return res.status(400) // TODO translate this
                        .message(`The OpenID Connect client ${client.name} is already associated with an existing application (${other_assoc_app.name}).`)
                        .api()
            }

            application.openid_client_ids = openid_client_ids
        }

        // Verify SAML service provider IDs
        const ServiceProvider = this.models.get('saml:ServiceProvider')
        if ( req.body.saml_service_provider_ids ) {
            const parsed = typeof req.body.saml_service_provider_ids === 'string' ? this.utility.infer(req.body.saml_service_provider_ids) : req.body.saml_service_provider_ids
            const saml_service_provider_ids = Array.isArray(parsed) ? parsed : [parsed]
            for ( const id of saml_service_provider_ids ) {
                const provider = await ServiceProvider.findById(id)
                if ( !provider || !provider.active || !req.user.can(`saml:provider:${provider.id}:view`) )
                    return res.status(400)
                        .message(`${req.T('api.invalid_saml_service_provider_id')} ${id}`)
                        .api()

                const other_assoc_app = await Application.findOne({ saml_service_provider_ids: provider.id })
                if ( other_assoc_app )
                    return res.status(400) // TODO translate this
                        .message(`The SAML service provider ${provider.name} is already associated with an existing application (${other_assoc_app.name}).`)
                        .api()
            }

            application.saml_service_provider_ids = saml_service_provider_ids
        }

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

    async update_application(req, res, next) {
        const Application = this.models.get('Application')
        const application = await Application.findById(req.params.id)

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

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

        const required_fields = ['name', 'identifier']
        for ( const field of required_fields ) {
            if ( !req.body[field] )
                return res.status(400)
                    .message(`${req.T('api.missing_field')} ${field}`)
                    .api()
        }

        // Make sure the identifier is properly formatted
        if ( !(new RegExp('^[a-zA-Z0-9_]*$')).test(req.body.identifier) )
            return res.status(400)
                .message(`${req.T('api.improper_field')} identifier ${req.T('api.alphanum_underscores')}`)
                .api()

        // Make sure the identifier is unique
        const existing_app = await Application.findOne({ identifier: req.body.identifier })
        if ( existing_app && existing_app.id !== application.id )
            return res.status(400)
                .message(req.T('api.application_already_exists'))
                .api()

        // Verify LDAP client IDs
        const LDAPClient = this.models.get('ldap:Client')
        if ( req.body.ldap_client_ids ) {
            const parsed = typeof req.body.ldap_client_ids === 'string' ? this.utility.infer(req.body.ldap_client_ids) : req.body.ldap_client_ids
            const ldap_client_ids = Array.isArray(parsed) ? parsed : [parsed]
            for ( const id of ldap_client_ids ) {
                const client = await LDAPClient.findById(id)
                if ( !client || !client.active || !req.user.can(`ldap:client:${client.id}:view`) )
                    return res.status(400)
                        .message(`${req.T('api.invalid_ldap_client_id')} ${id}`)
                        .api()

                const other_assoc_app = await Application.findOne({ ldap_client_ids: client.id })
                if ( other_assoc_app && other_assoc_app.id !== application.id )
                    return res.status(400) // TODO translate this
                        .message(`The LDAP client ${client.name} is already associated with an existing application (${other_assoc_app.name}).`)
                        .api()
            }

            application.ldap_client_ids = ldap_client_ids
        } else application.ldap_client_ids = []

        // Verify OAuth client IDs
        const OAuthClient = this.models.get('oauth:Client')
        if ( req.body.oauth_client_ids ) {
            const parsed = typeof req.body.oauth_client_ids === 'string' ? this.utility.infer(req.body.oauth_client_ids) : req.body.oauth_client_ids
            const oauth_client_ids = Array.isArray(parsed) ? parsed : [parsed]
            for ( const id of oauth_client_ids ) {
                const client = await OAuthClient.findById(id)
                if ( !client || !client.active || !req.user.can(`oauth:client:${client.id}:view`) )
                    return res.status(400)
                        .message(`${req.T('api.invalid_oauth_client_id')} ${id}`)
                        .api()

                const other_assoc_app = await Application.findOne({ oauth_client_ids: client.id })
                if ( other_assoc_app && other_assoc_app.id !== application.id )
                    return res.status(400) // TODO translate this
                        .message(`The OAuth2 client ${client.name} is already associated with an existing application (${other_assoc_app.name}).`)
                        .api()
            }

            application.oauth_client_ids = oauth_client_ids
        } else application.oauth_client_ids = []

        // Verify OpenID client IDs
        const OpenIDClient = this.models.get('openid:Client')
        if ( req.body.openid_client_ids ) {
            const parsed = typeof req.body.openid_client_ids === 'string' ? this.utility.infer(req.body.openid_client_ids) : req.body.openid_client_ids
            const openid_client_ids = Array.isArray(parsed) ? parsed : [parsed]
            for ( const id of openid_client_ids ) {
                const client = await OpenIDClient.findById(id)
                if ( !client )
                    return res.status(400)
                        .message(`${req.T('api.invalid_oauth_client_id')} ${id}`)
                        .api()

                const other_assoc_app = await Application.findOne({ openid_client_ids: client.id })
                if ( other_assoc_app && other_assoc_app.id !== application.id )
                    return res.status(400) // TODO translate this
                        .message(`The OpenID Connect client ${client.name} is already associated with an existing application (${other_assoc_app.name}).`)
                        .api()
            }

            application.openid_client_ids = openid_client_ids
        } else application.openid_client_ids = []

        // Verify SAML service provider IDs
        const ServiceProvider = this.models.get('saml:ServiceProvider')
        if ( req.body.saml_service_provider_ids ) {
            const parsed = typeof req.body.saml_service_provider_ids === 'string' ? this.utility.infer(req.body.saml_service_provider_ids) : req.body.saml_service_provider_ids
            const saml_service_provider_ids = Array.isArray(parsed) ? parsed : [parsed]
            for ( const id of saml_service_provider_ids ) {
                const provider = await ServiceProvider.findById(id)
                if ( !provider || !provider.active || !req.user.can(`saml:provider:${provider.id}:view`) )
                    return res.status(400)
                        .message(`${req.T('api.invalid_saml_service_provider_id')} ${id}`)
                        .api()

                const other_assoc_app = await Application.findOne({ saml_service_provider_ids: provider.id })
                if ( other_assoc_app && other_assoc_app.id !== application.id )
                    return res.status(400) // TODO translate this
                        .message(`The SAML service provider ${provider.name} is already associated with an existing application (${other_assoc_app.name}).`)
                        .api()
            }

            application.saml_service_provider_ids = saml_service_provider_ids
        } else application.saml_service_provider_ids = []

        application.name = req.body.name
        application.identifier = req.body.identifier
        application.description = req.body.description
        await application.save()
        return res.api(await application.to_api())
    }

    async delete_application(req, res, next) {
        const Application = this.models.get('Application')
        const application = await Application.findById(req.params.id)

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

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

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

module.exports = exports = AppController
