(function() {
    'use strict'

    async function authenticate(username, params) {

        try {
            const { authenticationOptionsEndpoint, authenticationEndpoint, statusServiceEndpoint, userVerification, originalResource, nevisAuthEndpoint } = params;
            const { startAuthentication } = SimpleWebAuthnBrowser;

            // fetch authentication options from nevisFIDO and save the returned fido2SessionId for later use
            const authOptRespJson = await getAuthenticationOptions(username, userVerification, nevisAuthEndpoint);
            const fido2SessionId = authOptRespJson.fido2SessionId;

            // do the client side authentication using the SimpleWebAuthn JS library
            const authRespJson = await startAuthentication(authOptRespJson);

            // in case the authentication response does not contain a userHandle (e.g. virtual authenticators used in system tests)
            // then we have to obtain it (in our case it is the IDM extId) using the Status Service since at the moment nevisFIDO always expects it
            if (!authRespJson.response.userHandle) {
                const statusRespJson = await getFido2SessionStatus(fido2SessionId, statusServiceEndpoint);

                if (statusRespJson && statusRespJson.userId) {
                    console.log("adding userHandle: " + statusRespJson.userId);
                    authRespJson.response.userHandle = btoa(statusRespJson.userId); // add missing userHandle
                }
                else {
                   throw new Error('userHandle is missing and could not determine it using the status service');
                }
            }
            else {
                console.log("userHandle already set: " + authRespJson.response.userHandle);
            }

            // send the assertion response created by the authenticator to nevisFIDO
            const serverRespJson = await submitAssertion(authRespJson, authenticationEndpoint);

            // checking the server response of nevisFIDO
            if ((!serverRespJson) || (serverRespJson && serverRespJson.status !== 'ok')) {
                let errorMessage = (serverRespJson && serverRespJson.errorMessage) ? serverRespJson.errorMessage : 'unexpected error';
                throw new Error('authentication failed: ' + errorMessage);
            }

            // send a request to nevisAuth with the fido2SessionId in the header to trigger the synchronisation of the
            // nevisFIDO and nevisAuth sessions (FIDO2 AuthState -> SyncFido2SessionStatusHandler) to reach AUTH_DONE
            await updateNevisAuth(fido2SessionId, nevisAuthEndpoint);

            console.log('authentication was successful');

            console.log('reloading page...');
            window.location.reload();
        }
        catch (error) {
            console.error(`Error during FIDO2 authentication: ${error}`);
            cancelFido2();
        }
    };

    async function getAuthenticationOptions(username, userVerification, authenticationOptionsEndpoint) {

        const authOptReqJson = {
            'username': username,
            'userVerification': userVerification,
        };

        const authOptReq = JSON.stringify(authOptReqJson);
        console.log('authOptReq ==> ' + authOptReq);

        const authOptResp = await fetch(authenticationOptionsEndpoint, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: authOptReq,
        });

        if (!authOptResp.ok) {
            throw new Error('authOptResp error: HTTP ' + authOptResp.status + ' ' + authOptResp.statusText);
        }

        const authOptRespJson = await authOptResp.json()
        console.log('authOptResp <== ' + JSON.stringify(authOptRespJson));

        return authOptRespJson;
    };

    async function getFido2SessionStatus(fido2SessionId, statusServiceEndpoint) {

        const statusReqJson = {
            'fido2SessionId': fido2SessionId,
        };

        const statusReq = JSON.stringify(statusReqJson);
        console.log('statusReq ==> ' + statusReq);

        const statusResp = await fetch(statusServiceEndpoint, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: statusReq,
        });

        if (!statusResp.ok) {
            throw new Error('statusResp error: HTTP ' + statusResp.status + ' ' + statusResp.statusText);
        }

        const statusRespJson = await statusResp.json();
        console.log('statusResp <== ' + JSON.stringify(statusRespJson));

        return statusRespJson;
    }

    async function submitAssertion(authRespJson, authenticationEndpoint) {

        console.log("submitting assertion for userHandle: " + authRespJson.response.userHandle);

        // TODO koenig 20230504: read btoa once nevisFIDO is adapted
        let encodedAuthResp = {
            "id": authRespJson.id,
            "response": {
                "authenticatorData": authRespJson.response.authenticatorData,
                "signature": authRespJson.response.signature,
                "userHandle": authRespJson.response.userHandle,
                "clientDataJSON": authRespJson.response.clientDataJSON
            },
            "type": authRespJson.type
        }

        const authResp = JSON.stringify(encodedAuthResp);
        console.log('authResp ==> ' + authResp);

        const serverResp = await fetch(authenticationEndpoint, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: authResp,
        });

        if (!serverResp.ok) {
            throw new Error('submitAssertion error: HTTP ' + submitAssertion.status + ' ' + submitAssertion.statusText);
        }

        const serverRespJson = await serverResp.json();
        console.log('serverResp <== ' + JSON.stringify(serverRespJson));

        return serverRespJson;
    };

    async function updateNevisAuth(fido2SessionId, nevisAuthEndpoint) {

        console.log('updateNevisAuth ==> ' + fido2SessionId);

        const updateNevisAuthResponse = await fetch(nevisAuthEndpoint, {
            method: 'GET',
            credentials: 'same-origin',
            headers: {
                'nevis-fido2-session-id': fido2SessionId,
            }
        });

        if (!updateNevisAuthResponse.ok) {
            throw new Error('updateNevisAuthResponse error: HTTP ' + updateNevisAuthResponse.status + ' ' + updateNevisAuthResponse.statusText);
        }

        console.log('updateNevisAuth <== OK');

        return;
    };

    // TODO koenig 20230206: we don't generate IDs into the HTML yet
    let username = document.getElementsByName("username")[0].value;
    params.nevisAuthEndpoint = window.location.href;
    authenticate(username, params);
})();
