import groovy.json.JsonSlurper
import groovy.json.JsonOutput

import ch.nevis.esauth.auth.engine.AuthUtil
import ch.nevis.esauth.util.httpclient.configuration.HttpClientConfiguration

/**
 *
 * Initiate the registration process with a `POST /nevisfido/token/dispatch/registration` to nevisFIDO.
 *
 * @param username - required
 * @param policy - default policy is used when null
 */
void dispatchRegistration(HttpClient httpClient, String baseUrl, String username, String policy) {

    LOG.debug(" ==> Starting out-of-band mobile onboarding for username '{}'.", username)

    String context = '{"username":"' + username + '", "policy":"' + (policy ?: 'default') + '"}'

    def jsonBody = JsonOutput.toJson([
        dispatcher: 'link',
        getUafRequest: [
            op: 'Reg',
            context: context
        ]
    ])

    LOG.debug("JSON body: {}", jsonBody)

    def fidoRequest = Http.post().url(baseUrl + "/token/dispatch/registration")
        .header('Accept', 'application/json; charset=utf-8')
        .header('Content-Type', 'application/json; charset=utf-8')
        .entity(Http.entity().content(jsonBody).build())
        .build()

    def fidoResponse = fidoRequest.send(httpClient)

    def responseCode = fidoResponse.code()
    if (responseCode != 200) {
        LOG.error('<== Failed to enroll user with username: ' + username + '. Response: ' + responseCode + ": " + fidoResponse.bodyAsString())
        response.setResult('failed')
        return
    }

    def json = new JsonSlurper().parseText(fidoResponse.bodyAsString())
    LOG.debug('JSON response: {}', json)

    if (json.dispatchResult != 'dispatched') {
        LOG.error('<== Failed to enroll user with username: ' + username + '. Response: ' + responseCode + ": " + fidoResponse.bodyAsString())
        response.setResult('failed')
        return
    }

    String dispatcherLink = json.dispatcherInformation.response
    String sessionId = json.sessionId

    // store dispatcher information and session ID as session variables
    // to handle page refresh and status polling

    // the session variable is rendered as a hidden field
    // and picked up by the mauth_onboard.js to render a link / QR code
    session.put('mauth_dispatcher_link', dispatcherLink)

    // the session ID is used to handle status polling
    session.put('mauth_session_id', sessionId)
}

/**
 *
 * Check registration status by sending a `POST /nevisfido/status` to nevisFIDO.
 *
 * @param sessionId - required
 */
void checkRegistrationStatus(HttpClient httpClient, String baseUrl, String sessionId) {

    LOG.debug(" ==> Checking out-of-band mobile registration status for session '{}'.", sessionId)

    def jsonBody = JsonOutput.toJson([
        sessionId: sessionId
    ])

    def fidoRequest = Http.post().url(baseUrl + "/status")
        .header('Accept', 'application/json; charset=utf-8')
        .header('Content-Type', 'application/json; charset=utf-8')
        .entity(Http.entity().content(jsonBody).build())
        .build()

    def fidoResponse = fidoRequest.send(httpClient)

    def responseCode = fidoResponse.code()
    if (responseCode == 200) {

        def json = new JsonSlurper().parseText(fidoResponse.bodyAsString())
        LOG.debug('JSON response: {}', json)

        String status = json.status
        LOG.debug('status: {}', status)

        def inctx = request.getLoginContext()

        def contentType = request.getHttpHeader('Content-Type')
        LOG.debug("incoming request has Content-Type: {}", contentType)

        if (contentType ==~ /.*json.*/) {
            LOG.debug("detected AJAX call")

            // responding AJAX call from JS and returning only the status (nothing else)
            def statusJson = JsonOutput.toJson([
                status: status
            ])

            response.setContent(statusJson)
            response.setContentType('application/json')
            response.setHttpStatusCode(200)
            response.setIsDirectResponse(true)
        }
        else {
            // this is a form POST and thus we have to check if we have to continue
            if (status == 'succeeded') {
                response.setResult('ok')
                return
            }
        }
    }
    else {
        LOG.error('<== Failed to check status for session ' + sessionId + '. Response: ' + responseCode + ": " + fidoResponse.bodyAsString())
        response.setResult('failed')
    }
}

// convert parameters to Properties
Properties properties = new Properties()
for (Map.Entry<String, String> entry : parameters.entrySet()) {
    properties.setProperty(entry.getKey(), entry.getValue())
}

def httpClientConfig = HttpClientConfiguration.from(properties, request, response)

// we cannot use a cached HTTP client here as the parameters contain expressions that depend on the request
def httpClient = HttpClients.create(httpClientConfig)

def baseUrl = parameters.get('fidoUrl')

if (session.containsKey('mauth_session_id')) {

    def sessionId = session['mauth_session_id']

    // mauth_onboard.js sends empty AJAX calls to check for completion.
    // we have to check the status by sending a `POST /nevisfido/status` to nevisFIDO respond to the AJAX call.
    checkRegistrationStatus(httpClient, baseUrl, sessionId)
}
else {
    def usernameSource = parameters.get('username')
    if (usernameSource == null || usernameSource.isBlank()) {
        LOG.error('out-of-band mobile onboarding failed. no expression to determine username.')
        response.setResult('failed')
        return
    }

    def username = AuthUtil.substituteVariables(request, response, usernameSource)
    if (username == null || username.isBlank()) {
        LOG.error('out-of-band mobile onboarding failed. missing username.')
        response.setResult('failed')
        return
    }

    def policy = parameters.get('policy')
    dispatchRegistration(httpClient, baseUrl, username, policy)
}
