// Needs to be a var at the top level to get hoisted to global scope.
// https://stackoverflow.com/questions/28776079/do-let-statements-create-properties-on-the-global-object/28776236#28776236
var aadOauth = (function () {
  let myMSALObj = null;
  let authResult = null;
  let redirectHandlerTask = null;

  const tokenRequest = {
    scopes: null,
    prompt: null,
    extraQueryParameters: {},
    loginHint: null
  };

  // Initialise the myMSALObj for the given client, authority and scope
 function init(config) {
     // TODO: Add support for other MSAL configuration
     var authData = {
         clientId: config.clientId,
         authority: config.isB2C ? "https://" + config.tenant + ".b2clogin.com/tfp/" + config.tenant + ".onmicrosoft.com/" + config.policy + "/" : "https://login.microsoftonline.com/" + config.tenant,
         knownAuthorities: [ config.tenant + ".b2clogin.com", "login.microsoftonline.com"],
         redirectUri: config.redirectUri,
     };
     var postLogoutRedirectUri = {
         postLogoutRedirectUri: config.postLogoutRedirectUri,
     };
     var msalConfig = {
         auth: config?.postLogoutRedirectUri == null ? {
             ...authData,
         } : {
             ...authData,
             ...postLogoutRedirectUri,
         },
         cache: {
             cacheLocation: config.cacheLocation,
             storeAuthStateInCookie: false,
         },
     };

     if (typeof config.scope === "string") {
         tokenRequest.scopes = config.scope.split(" ");
     } else {
         tokenRequest.scopes = config.scope;
     }

     tokenRequest.extraQueryParameters = JSON.parse(config.customParameters);
     tokenRequest.prompt = config.prompt;
     tokenRequest.loginHint = config.loginHint;

     myMSALObj = new msal.PublicClientApplication(msalConfig);
     // Register Callbacks for Redirect flow and record the task so we
     // can await its completion in the login API

     redirectHandlerTask = myMSALObj.handleRedirectPromise();
 }

  // Tries to silently acquire a token. Will return null if a token
  // could not be acquired or if no cached account credentials exist.
  // Will return the authentication result on success and update the
  // global authResult variable.
  async function silentlyAcquireToken() {
    const account = getAccount();
    if (account == null) {
      return null;
    }

    try {
      // Silent acquisition only works if the access token is either
      // within its lifetime, or the refresh token can successfully be
      // used to refresh it. This will throw if the access token can't
      // be acquired.
      const silentAuthResult = await myMSALObj.acquireTokenSilent({
        scopes: tokenRequest.scopes,
        prompt: "none",
        account: account,
        extraQueryParameters: tokenRequest.extraQueryParameters
      });

      return  authResult = silentAuthResult;
    } catch (error) {
      console.log('Unable to silently acquire a new token: ' + error.message)
      return null;
    }

  }

  /// Authorize user via refresh token or web gui if necessary.
  ///
  /// Setting [refreshIfAvailable] to [true] should attempt to re-authenticate
  /// with the existing refresh token, if any, even though the access token may
  /// still be valid; however MSAL doesn't support this. Therefore it will have
  /// the same impact as when it is set to [false].
  /// [useRedirect] uses the MSAL redirection based token acquisition instead of
  /// a popup window. This is the only way that iOS based devices will acquire
  /// a token using MSAL when the application is installed to the home screen.
  /// This is because the popup window operates outside the sandbox of the PWA and
  /// won't share cookies or local storage with the PWA sandbox. Redirect flow works
  /// around this issue by having the MSAL authentication take place directly within
  /// the PWA sandbox browser.
  /// The token is requested using acquireTokenSilent, which will refresh the token
  /// if it has nearly expired. If this fails for any reason, it will then move on
  /// to attempt to refresh the token using an interactive login.

  async function login(refreshIfAvailable, useRedirect, onSuccess, onError) {
    try {
      // The redirect handler task will complete with auth results if we
      // were redirected from AAD. If not, it will complete with null
      // We must wait for it to complete before we allow the login to
      // attempt to acquire a token silently, and then progress to interactive
      // login (if silent acquisition fails).
      let result = await redirectHandlerTask;
      if (result !== null) {
        authResult = result;
      }
    }
    catch (error) {
      authResultError = error;
      onError(authResultError);
      return;
    }

    // Try to sign in silently, assuming we have already signed in and have
    // a cached access token
    await silentlyAcquireToken()

    if(authResult != null) {
      // Skip interactive login
      onSuccess(authResult.accessToken ?? null);
      return
    }

    const account = getAccount()

    if (useRedirect) {
      myMSALObj.acquireTokenRedirect({
        scopes: tokenRequest.scopes,
        prompt: tokenRequest.prompt,
        account: account,
        extraQueryParameters: tokenRequest.extraQueryParameters,
        loginHint: tokenRequest.loginHint
      });
    } else {
      // Sign in with popup
      try {
        const interactiveAuthResult = await myMSALObj.loginPopup({
          scopes: tokenRequest.scopes,
          prompt: tokenRequest.prompt,
          account: account,
          extraQueryParameters: tokenRequest.extraQueryParameters,
          loginHint: tokenRequest.loginHint
        });

        authResult = interactiveAuthResult;

        onSuccess(authResult.accessToken ?? null);
      } catch (error) {
        // rethrow
        console.warn(error.message);
        onError(error);
      }
    }
  }

  // Tries to refresh the token. Will call [onError] if a token
  // could not be acquired or if no cached account credentials exist.
  // Will call [onSuccess] on success and update the global authResult variable.
  async function refreshToken(onSuccess, onError) {
    try {
      // The redirect handler task will complete with auth results if we
      // were redirected from AAD. If not, it will complete with null
      // We must wait for it to complete before we allow the login to
      // attempt to acquire a token silently, and then progress to interactive
      // login (if silent acquisition fails).
      let result = await redirectHandlerTask;
      if (result !== null) {
        authResult = result;
      }
    }
    catch (error) {
      authResultError = error;
      onError(authResultError);
      return;
    }

    // Try to sign in silently, assuming we have already signed in and have
    // a cached access token
    await silentlyAcquireToken()

    if(authResult != null) {
      onSuccess(authResult.accessToken ?? null);
      return
    }
  }

  function getAccount() {
    // If we have recently authenticated, we use the auth'd account;
    // otherwise we fallback to using MSAL APIs to find cached auth
    // accounts in browser storage.
    if (authResult !== null && authResult.account !== null) {
      return authResult.account
    }

    const currentAccounts = myMSALObj.getAllAccounts();

    if (currentAccounts === null || currentAccounts.length === 0) {
      return null;
    } else if (currentAccounts.length > 1) {
      // Multiple users - pick the first one, but this shouldn't happen
      console.warn("Multiple accounts detected, selecting first.");

      return currentAccounts[0];
    } else if (currentAccounts.length === 1) {
      return currentAccounts[0];
    }
  }

  function logout(onSuccess, onError, showPopup) {
    const account = getAccount();

    if (!account) {
      onSuccess();
      return;
    }

    authResult = null;
    authResultError = null;
    tokenRequest.scopes = null;

    if (showPopup) {
      myMSALObj
        .logout({ account: account })
        .then((_) => onSuccess())
        .catch(onError);
    } else {
      myMSALObj
        .logoutRedirect({
          account: account,
          onRedirectNavigate: (url) => {
            return false;
          }
        })
        .then((_) => onSuccess())
        .catch(onError);
    }


  }

  async function getAccessToken() {
    var result = await silentlyAcquireToken()
    return result ? result.accessToken : null;
  }

  async function getIdToken() {
    var result = await silentlyAcquireToken()
    return result ? result.idToken : null;
  }

  function hasCachedAccountInformation() {
    return getAccount() != null;
  }

  return {
    init: init,
    login: login,
    refreshToken: refreshToken,
    logout: logout,
    getIdToken: getIdToken,
    getAccessToken: getAccessToken,
    hasCachedAccountInformation: hasCachedAccountInformation,
  };
})();
