/* (non-Javadoc)
   * @see org.wso2.carbon.identity.application.authentication.framework.AbstractApplicationAuthenticator#initiateAuthenticationRequest(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext)
   */
  @Override
  protected void initiateAuthenticationRequest(
      HttpServletRequest request, HttpServletResponse response, AuthenticationContext context)
      throws AuthenticationFailedException {

    String loginPage = ConfigurationFacade.getInstance().getAuthenticationEndpointURL();

    String queryParams =
        FrameworkUtils.getQueryStringWithFrameworkContextId(
            context.getQueryParams(),
            context.getCallerSessionKey(),
            context.getContextIdentifier());

    try {

      String retryParam = "";

      if (context.isRetrying()) {
        retryParam = "&authFailure=true&authFailureMsg=login.fail.message";
      } else {
        // Insert entry to DB only if this is not a retry
        DBUtils.insertUserResponse(
            context.getContextIdentifier(), String.valueOf(MSSAuthenticator.UserResponse.PENDING));
      }

      // MSISDN will be saved in the context in the MSISDNAuthenticator
      String msisdn = (String) context.getProperty("msisdn");
      MSSRequest mssRequest = new MSSRequest();
      mssRequest.setMsisdnNo("+" + msisdn);
      mssRequest.setSendString(
          DataHolder.getInstance().getMobileConnectConfig().getMSS().getMssText());

      String contextIdentifier = context.getContextIdentifier();
      MSSRestClient mssRestClient = new MSSRestClient(contextIdentifier, mssRequest);
      mssRestClient.start();

      response.sendRedirect(
          response.encodeRedirectURL(loginPage + ("?" + queryParams))
              + "&authenticators="
              + getName()
              + ":"
              + "LOCAL"
              + retryParam);

    } catch (IOException e) {
      throw new AuthenticationFailedException(e.getMessage(), e);
    } catch (AuthenticatorException e) {
      throw new AuthenticationFailedException(e.getMessage(), e);
    }
  }
  /* (non-Javadoc)
   * @see org.wso2.carbon.identity.application.authentication.framework.AbstractApplicationAuthenticator#processAuthenticationResponse(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext)
   */
  @Override
  protected void processAuthenticationResponse(
      HttpServletRequest httpServletRequest,
      HttpServletResponse httpServletResponse,
      AuthenticationContext context)
      throws AuthenticationFailedException {

    // String msisdn = httpServletRequest.getParameter("msisdn");
    log.info("MSS PIN Authenticator authentication Start ");
    String sessionDataKey = httpServletRequest.getParameter("sessionDataKey");
    String msisdn = (String) context.getProperty("msisdn");
    boolean isAuthenticated = false;

    try {
      String responseStatus = DBUtils.getUserResponse(sessionDataKey);

      if (responseStatus.equalsIgnoreCase(UserResponse.APPROVED.toString())) {
        isAuthenticated = true;
      }

    } catch (AuthenticatorException e) {
      log.error("SMS Authentication failed while trying to authenticate", e);
      throw new AuthenticationFailedException(e.getMessage(), e);
    }

    if (!isAuthenticated) {
      log.info("MSS PIN Authenticator authentication failed ");
      context.setProperty("faileduser", msisdn);
      if (log.isDebugEnabled()) {
        log.debug("User authentication failed due to not existing user MSISDN.");
      }

      throw new AuthenticationFailedException("Authentication Failed");
    }
    log.info("MSS PIN Authenticator authentication success for MSISDN - " + msisdn);

    context.setProperty("msisdn", msisdn);
    // context.setSubject(msisdn);
    //        AuthenticatedUser user=new AuthenticatedUser();
    //		context.setSubject(user);
    AuthenticationContextHelper.setSubject(context, msisdn);
    String rememberMe = httpServletRequest.getParameter("chkRemember");

    if (rememberMe != null && "on".equals(rememberMe)) {
      context.setRememberMe(true);
    }
  }
  /**
   * this method are overridden for extra claim request to google end-point
   *
   * @param request
   * @param response
   * @param context
   * @throws AuthenticationFailedException
   */
  @Override
  protected void processAuthenticationResponse(
      HttpServletRequest request, HttpServletResponse response, AuthenticationContext context)
      throws AuthenticationFailedException {

    try {

      Map<String, String> authenticatorProperties = context.getAuthenticatorProperties();
      String clientId = authenticatorProperties.get(OIDCAuthenticatorConstants.CLIENT_ID);
      String clientSecret = authenticatorProperties.get(OIDCAuthenticatorConstants.CLIENT_SECRET);
      String tokenEndPoint;
      if (getTokenEndpoint(authenticatorProperties) != null) {
        tokenEndPoint = getTokenEndpoint(authenticatorProperties);
      } else {
        tokenEndPoint = authenticatorProperties.get(OIDCAuthenticatorConstants.OAUTH2_TOKEN_URL);
      }

      String callBackUrl =
          authenticatorProperties.get(GoogleOAuth2AuthenticationConstant.CALLBACK_URL);

      log.debug("callBackUrl : " + callBackUrl);

      if (callBackUrl == null) {
        callBackUrl = CarbonUIUtil.getAdminConsoleURL(request);
        callBackUrl = callBackUrl.replace("commonauth/carbon/", "commonauth");
      }

      @SuppressWarnings({"unchecked"})
      Map<String, String> paramValueMap =
          (Map<String, String>) context.getProperty("oidc:param.map");

      if (paramValueMap != null && paramValueMap.containsKey("redirect_uri")) {
        callBackUrl = paramValueMap.get("redirect_uri");
      }

      OAuthAuthzResponse authzResponse = OAuthAuthzResponse.oauthCodeAuthzResponse(request);
      String code = authzResponse.getCode();

      OAuthClientRequest accessRequest = null;
      accessRequest = getAccessRequest(tokenEndPoint, clientId, clientSecret, callBackUrl, code);

      // create OAuth client that uses custom http client under the hood
      OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
      OAuthClientResponse oAuthResponse = null;
      oAuthResponse = getOAuthResponse(accessRequest, oAuthClient, oAuthResponse);
      // TODO : return access token and id token to framework
      String accessToken = "";
      String idToken = "";
      if (oAuthResponse != null) {
        accessToken = oAuthResponse.getParam(OIDCAuthenticatorConstants.ACCESS_TOKEN);
        idToken = oAuthResponse.getParam(OIDCAuthenticatorConstants.ID_TOKEN);
      }

      if (accessToken != null && (idToken != null || !requiredIDToken(authenticatorProperties))) {

        context.setProperty(OIDCAuthenticatorConstants.ACCESS_TOKEN, accessToken);

        if (idToken != null) {
          context.setProperty(OIDCAuthenticatorConstants.ID_TOKEN, idToken);

          String base64Body = idToken.split("\\.")[1];
          byte[] decoded = Base64.decodeBase64(base64Body.getBytes());
          String json = new String(decoded, Charset.forName("utf-8"));

          if (log.isDebugEnabled()) {
            log.debug("Id token json string : " + json);
          }

          Map<String, Object> jsonObject = JSONUtils.parseJSON(json);

          if (jsonObject != null) {
            Map<ClaimMapping, String> claims = getSubjectAttributes(oAuthResponse);

            String authenticatedUser =
                (String) jsonObject.get(OIDCAuthenticatorConstants.Claim.EMAIL);
            AuthenticatedUser authenticatedUserObj =
                AuthenticatedUser.createFederateAuthenticatedUserFromSubjectIdentifier(
                    authenticatedUser);
            authenticatedUserObj.setUserAttributes(claims);
            context.setSubject(authenticatedUserObj);
          } else {
            if (log.isDebugEnabled()) {
              log.debug("Decoded json object is null");
            }
            throw new AuthenticationFailedException("Decoded json object is null");
          }
        } else {
          if (log.isDebugEnabled()) {
            log.debug("Authentication Failed");
          }
          throw new AuthenticationFailedException("Authentication Failed");
        }

      } else {
        throw new AuthenticationFailedException("Authentication Failed");
      }
    } catch (OAuthProblemException e) {
      throw new AuthenticationFailedException("Error occurred while acquiring access token", e);
    } catch (JSONException e) {
      throw new AuthenticationFailedException("Error occurred while parsing json object", e);
    }
  }
  @Override
  protected void processAuthenticationResponse(
      HttpServletRequest request, HttpServletResponse response, AuthenticationContext context)
      throws AuthenticationFailedException {

    try {

      Map<String, String> authenticatorProperties = context.getAuthenticatorProperties();

      String clientId = authenticatorProperties.get(OIDCAuthenticatorConstants.CLIENT_ID);
      String clientSecret = authenticatorProperties.get(OIDCAuthenticatorConstants.CLIENT_SECRET);
      String tokenEndPoint = getTokenEndpoint(authenticatorProperties);

      if (tokenEndPoint == null) {
        tokenEndPoint = authenticatorProperties.get(OIDCAuthenticatorConstants.OAUTH2_TOKEN_URL);
      }

      String callbackUrl = getCallbackUrl(authenticatorProperties);

      if (StringUtils.isBlank(callbackUrl)) {
        callbackUrl = IdentityUtil.getServerURL(FrameworkConstants.COMMONAUTH, true, true);
      }

      @SuppressWarnings({"unchecked"})
      Map<String, String> paramValueMap =
          (Map<String, String>) context.getProperty("oidc:param.map");

      if (paramValueMap != null && paramValueMap.containsKey("redirect_uri")) {
        callbackUrl = paramValueMap.get("redirect_uri");
      }

      OAuthAuthzResponse authzResponse = OAuthAuthzResponse.oauthCodeAuthzResponse(request);
      String code = authzResponse.getCode();

      OAuthClientRequest accessRequest =
          getaccessRequest(tokenEndPoint, clientId, code, clientSecret, callbackUrl);

      // Create OAuth client that uses custom http client under the hood
      OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
      OAuthClientResponse oAuthResponse = getOauthResponse(oAuthClient, accessRequest);

      // TODO : return access token and id token to framework
      String accessToken = oAuthResponse.getParam(OIDCAuthenticatorConstants.ACCESS_TOKEN);

      if (StringUtils.isBlank(accessToken)) {
        throw new AuthenticationFailedException("Access token is empty or null");
      }

      String idToken = oAuthResponse.getParam(OIDCAuthenticatorConstants.ID_TOKEN);

      if (StringUtils.isBlank(idToken) && requiredIDToken(authenticatorProperties)) {
        throw new AuthenticationFailedException("Id token is required and is missing");
      }

      context.setProperty(OIDCAuthenticatorConstants.ACCESS_TOKEN, accessToken);

      AuthenticatedUser authenticatedUserObj;
      Map<ClaimMapping, String> claims = new HashMap<>();
      Map<String, Object> jsonObject = new HashMap<>();

      if (StringUtils.isNotBlank(idToken)) {

        context.setProperty(OIDCAuthenticatorConstants.ID_TOKEN, idToken);

        String base64Body = idToken.split("\\.")[1];
        byte[] decoded = Base64.decodeBase64(base64Body.getBytes());
        String json = new String(decoded);

        jsonObject = JSONUtils.parseJSON(json);

        if (jsonObject == null) {

          if (log.isDebugEnabled()) {
            log.debug("Decoded json object is null");
          }

          throw new AuthenticationFailedException("Decoded json object is null");
        }

        if (log.isDebugEnabled()
            && IdentityUtil.isTokenLoggable(IdentityConstants.IdentityTokens.USER_ID_TOKEN)) {
          log.debug("Retrieved the User Information:" + jsonObject);
        }

        String authenticatedUser = null;
        String isSubjectInClaimsProp =
            context
                .getAuthenticatorProperties()
                .get(IdentityApplicationConstants.Authenticator.SAML2SSO.IS_USER_ID_IN_CLAIMS);

        if (StringUtils.equalsIgnoreCase("true", isSubjectInClaimsProp)) {

          authenticatedUser = getSubjectFromUserIDClaimURI(context);

          if (authenticatedUser == null && log.isDebugEnabled()) {
            log.debug(
                "Subject claim could not be found amongst subject attributes. "
                    + "Defaulting to the sub attribute in IDToken.");
          }
        }

        if (authenticatedUser == null) {

          authenticatedUser = getAuthenticateUser(context, jsonObject, oAuthResponse);

          if (authenticatedUser == null) {
            throw new AuthenticationFailedException("Cannot find federated User Identifier");
          }
        }

        String attributeSeparator = null;

        try {

          String tenantDomain = context.getTenantDomain();

          if (StringUtils.isBlank(tenantDomain)) {
            tenantDomain = MultitenantConstants.SUPER_TENANT_DOMAIN_NAME;
          }

          int tenantId =
              OpenIDConnectAuthenticatorServiceComponent.getRealmService()
                  .getTenantManager()
                  .getTenantId(tenantDomain);
          UserRealm userRealm =
              OpenIDConnectAuthenticatorServiceComponent.getRealmService()
                  .getTenantUserRealm(tenantId);

          if (userRealm != null) {
            UserStoreManager userStore = (UserStoreManager) userRealm.getUserStoreManager();
            attributeSeparator =
                userStore
                    .getRealmConfiguration()
                    .getUserStoreProperty(IdentityCoreConstants.MULTI_ATTRIBUTE_SEPARATOR);
            if (log.isDebugEnabled()) {
              log.debug(
                  "For the claim mapping: "
                      + attributeSeparator
                      + " is used as the attributeSeparator in tenant: "
                      + tenantDomain);
            }
          }

        } catch (UserStoreException e) {
          throw new AuthenticationFailedException(
              "Error while retrieving multi attribute " + "separator", e);
        }

        for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
          buildClaimMappings(claims, entry, attributeSeparator);
        }

        authenticatedUserObj =
            AuthenticatedUser.createFederateAuthenticatedUserFromSubjectIdentifier(
                authenticatedUser);
      } else {

        if (log.isDebugEnabled()) {
          log.debug("The IdToken is null");
        }

        authenticatedUserObj =
            AuthenticatedUser.createFederateAuthenticatedUserFromSubjectIdentifier(
                getAuthenticateUser(context, jsonObject, oAuthResponse));
      }

      claims.putAll(getSubjectAttributes(oAuthResponse, authenticatorProperties));
      authenticatedUserObj.setUserAttributes(claims);

      context.setSubject(authenticatedUserObj);

    } catch (OAuthProblemException e) {
      throw new AuthenticationFailedException("Authentication process failed", e);
    }
  }