/*
  User session script
  Version: 1.0.2
*/

function getCognitoConfig() {
  return {
    region: 'us-east-1',
    userPoolId: 'us-east-1_uG9SGX7Wd',
  };
};

function getCognitoIssuerUrl(cognitoCfg) {
  return 'https://cognito-idp.' + cognitoCfg.region + '.amazonaws.com/' + cognitoCfg.userPoolId;
};

function verifyUserSession(
  cognitoCfg,
  userSession, // cookie
  accessToken, // cookie
) {
  var output = {
    error: {
      code: '0',
    },
    data: {},
  };

  if (
    typeof accessToken === 'undefined'
    || !accessToken
  ) {
    // cookie access token will vanish after expiration .. the presence of "userSession" is an indicator that there was a session before
    if (
      typeof userSession !== 'undefined'
      && userSession
    ) {
      if (userSession === '-1') {
        output.error = {
          code: 'USSBR',
          description: 'User session should be refreshed'
        };

        return output;
      }

      output.error = {
        code: 'USHE',
        description: 'User session has expired'
      };

      return output;
    }

    output.error = {
      code: 'USNF', // previously called ATINS
      description: 'User session not found'
    };

    return output;
  } else if (accessToken === '-1') { // legacy fallback
    output.error = {
      code: 'LUSSBR',
      description: 'User session should be refreshed'
    };

    return output;
  }

  var accessTokenMeta = parseAccessToken(accessToken);

  if (accessTokenMeta.error.code !== '0') {
    output.error = accessTokenMeta.error;

    return output;
  }

  output.data = accessTokenMeta.data;

  var claim = accessTokenMeta.data.payload;

  var issuer = getCognitoIssuerUrl(cognitoCfg);

  if (claim.iss !== issuer) {
    output.error = {
      code: 'ATINVCIDNM',
      description: 'Access token is not valid. Claim issuer does not match',
      meta: {
        accessToken: accessToken,
        issuer: issuer,
        claim: claim,
      }
    };

    return output;
  }

  if (claim.token_use !== 'access') {
    output.error = {
      code: 'ATINVCUINA',
      description: 'Access token is not valid. Claim use is not access',
      meta: {
        accessToken: accessToken,
        claim: claim,
      }
    };

    return output;
  }

  return output;
};

function decodeBase64UrlEncodedJwtSection(encodedStr) {
  // https://stackoverflow.com/questions/38552003/how-to-decode-jwt-token-in-javascript-without-using-a-library
  var output = {
    error: {
      code: '0'
    },
    data: '',
  };

  try {
    encodedStr = encodedStr
      .replace(/-/g, '+')
      .replace(/_/g, '/');

    output.data = decodeURIComponent(atob(encodedStr)
      .split('')
      .map(function (char) {
        return '%' + ('00' + char.charCodeAt(0).toString(16)).slice(-2);
      })
      .join(''));
  } catch (exc) {
    output.error = getMetaPreparedFromException(exc);
  }

  return output;
};

function parseAccessToken(
  accessToken,
  parseHeader,
) {
  var output = {
    error: {
      code: '0'
    },
    data: {},
  };

  if (
    typeof accessToken === 'undefined'
    || !accessToken
  ) {
    output.error = {
      code: 'USNF', // previously called ATINS
      description: 'User session not found'
    };

    return output;
  } else if (accessToken === '-1') { // legacy fallback
    output.error = {
      code: 'USSBR',
      description: 'User session should be refreshed'
    };

    return output;
  }

  try {
    var accessTokenSections = accessToken.split('.');

    if (accessTokenSections.length < 3) { // maybe in future we may have more than 3 chunks
      output.error = {
        code: 'ATINVWNOTS',
        description: 'Access token is not valid. Wrong number of token sections',
        meta: {
          accessToken: accessToken,
          accessTokenSections: accessTokenSections,
        }
      };

      return output;
    }

    var accessTokenHeader = undefined;

    if (typeof parseHeader !== 'undefined' && parseHeader) {
      var accessTokenHeaderEncodedStr = accessTokenSections[0];

      var accessTokenHeaderDecodedStrRes = decodeBase64UrlEncodedJwtSection(accessTokenHeaderEncodedStr);

      if (accessTokenHeaderDecodedStrRes.error.code !== '0') {
        output.error = {
          code: 'ATINVFDTTH',
          description: 'Access token is not valid. Failed decoding the token header',
          meta: {
            accessToken: accessToken,
            accessTokenHeaderEncodedStr: accessTokenHeaderEncodedStr,
            decodingError: accessTokenHeaderDecodedStrRes.error
          }
        };

        return output;
      }

      var accessTokenHeaderParseRes = parseJson(
        accessTokenHeaderDecodedStrRes.data, // jsonStr
        ['kid', 'alg'], // requiredFields
      );

      if (accessTokenHeaderParseRes.error.code !== '0') {
        output.error = {
          code: 'ATINVFPTH',
          description: 'Access token is not valid. Failed parsing token header',
          meta: {
            accessToken: accessToken,
            accessTokenHeaderEncodedStr: accessTokenHeaderEncodedStr,
            accessTokenHeaderDecodedStr: accessTokenHeaderDecodedStrRes.data,
            parseError: accessTokenHeaderParseRes.error
          }
        };

        return output;
      }

      accessTokenHeader = accessTokenHeaderParseRes.data;
    }

    var accessTokenPayloadEncodedStr = accessTokenSections[1];

    var accessTokenPayloadDecodedStrRes = decodeBase64UrlEncodedJwtSection(accessTokenPayloadEncodedStr);

    if (accessTokenPayloadDecodedStrRes.error.code !== '0') {
      output.error = {
        code: 'ATINVFDTTP',
        description: 'Access token is not valid. Failed decoding the token payload',
        meta: {
          accessToken: accessToken,
          accessTokenPayloadEncodedStr: accessTokenPayloadEncodedStr,
          decodingError: accessTokenPayloadDecodedStrRes.error
        }
      };

      return output;
    }

    var accessTokenPayloadParseRes = parseJson(
      accessTokenPayloadDecodedStrRes.data, // jsonStr
      [
        'sub',
        'event_id',
        'token_use',
        'scope',
        'auth_time',
        'iss',
        'exp',
        'iat',
        'jti',
        'client_id',
        'username',
      ], // requiredFields
    );

    if (accessTokenPayloadParseRes.error.code !== '0') {
      output.error = {
        code: 'ATINVFPTP',
        description: 'Access token is not valid. Failed parsing token payload',
        meta: {
          accessToken: accessToken,
          accessTokenPayloadEncodedStr: accessTokenPayloadEncodedStr,
          accessTokenPayloadDecodedStr: accessTokenPayloadDecodedStrRes.data,
          parseError: accessTokenPayloadParseRes.error
        }
      };

      return output;
    }

    var accessTokenPayload = accessTokenPayloadParseRes.data;

    var accessTokenExpiryUts = parseInt(accessTokenPayload.exp);

    var accessTokenExpiryDto = new Date(accessTokenExpiryUts * 1000);

    output.data = {
      rawStr: accessToken,
      header: accessTokenHeader,
      payload: accessTokenPayload,
      expiryDto: accessTokenExpiryDto,
      expiryUts: accessTokenExpiryUts,
      // expired: getCurrentUts() >= accessTokenExpiryUts,
    };
  } catch (exc) {
    output.error = getMetaPreparedFromException(exc);
  }

  return output;
};

function refreshUserSessionViaRedirect(redirectUrl) {
  // console.log(getCurrentUtus().toString() + ' refreshUserSessionViaRedirect -> initialized -> redirectUrl: ', redirectUrl);
  Cookies.remove('accessToken'); // extra/safety removal
  Cookies.remove('userSession'); // extra/safety removal

  Cookies.set('userSession', '-1', {
    domain: '.cwaschools.com',
    path: '/',
    secure: true,
    sameSite: 'strict',
  });

  // return;

  window.location.href = 'https://profile.cwaschools.com/refresh-session?redirect_url=' + encodeURIComponent(redirectUrl);
};

// < utils
function getMetaPreparedFromException(exc) {
  var output = {
    code: '1',
    description: 'Internal server error',
  };

  if (exc instanceof Error) {
    output.description = exc.message;
  } else if (typeof exc === 'string') {
    output.description = exc;
  }

  return output;
};

function parseJson(
  jsonStr,
  requiredFields,
) {
  var output = {
    error: {
      code: '0'
    },
    data: {},
  };

  try {
    output.data = JSON.parse(jsonStr);

    if (typeof requiredFields !== 'undefined') {
      for (var i = 0; i < requiredFields.length; i++) {
        var key = requiredFields[i];

        if (
          typeof output.data[key] === 'undefined'
        ) {
          output.error = {
            code: 'FINPID',
            description: 'Field is not present in data',
            meta: {
              key: key,
            }
          };

          return output;
        }
      }
    }
  } catch (exc) {
    output.error = getMetaPreparedFromException(exc);
  }

  return output;
};
// > utils