/**
   * This method handles authentication and sends authentication Response message back to the
   * Service Provider after successful authentication. In case of authentication failure the user is
   * prompted back for authentication.
   *
   * @param req
   * @param resp
   * @param sessionId
   * @throws IdentityException
   * @throws IOException
   * @throws ServletException
   */
  private void handleAuthenticationReponseFromFramework(
      HttpServletRequest req,
      HttpServletResponse resp,
      String sessionId,
      SAMLSSOSessionDTO sessionDTO)
      throws UserStoreException, IdentityException, IOException, ServletException {

    String sessionDataKey = CharacterEncoder.getSafeText(req.getParameter("sessionDataKey"));
    AuthenticationResult authResult = getAuthenticationResultFromCache(sessionDataKey);

    if (log.isDebugEnabled() && authResult == null) {
      log.debug("Session data is not found for key : " + sessionDataKey);
    }
    SAMLSSOReqValidationResponseDTO reqValidationDTO = sessionDTO.getValidationRespDTO();
    SAMLSSOAuthnReqDTO authnReqDTO = new SAMLSSOAuthnReqDTO();

    if (authResult == null || !authResult.isAuthenticated()) {

      if (log.isDebugEnabled() && authResult != null) {
        log.debug("Unauthenticated User");
      }

      if (reqValidationDTO.isPassive()) { // if passive

        List<String> statusCodes = new ArrayList<String>();
        statusCodes.add(SAMLSSOConstants.StatusCodes.NO_PASSIVE);
        statusCodes.add(SAMLSSOConstants.StatusCodes.IDENTITY_PROVIDER_ERROR);
        String destination = reqValidationDTO.getDestination();
        reqValidationDTO.setResponse(
            SAMLSSOUtil.buildErrorResponse(
                reqValidationDTO.getId(),
                statusCodes,
                "Cannot authenticate Subject in Passive Mode",
                destination));

        sendResponse(
            req,
            resp,
            sessionDTO.getRelayState(),
            reqValidationDTO.getResponse(),
            reqValidationDTO.getAssertionConsumerURL(),
            reqValidationDTO.getSubject(),
            null,
            sessionDTO.getTenantDomain());
        return;

      } else { // if forceAuthn or normal flow
        // TODO send a saml response with a status message.
        if (!authResult.isAuthenticated()) {
          String destination = reqValidationDTO.getDestination();
          String errorResp =
              SAMLSSOUtil.buildErrorResponse(
                  SAMLSSOConstants.StatusCodes.AUTHN_FAILURE,
                  "User authentication failed",
                  destination);
          sendNotification(
              errorResp,
              SAMLSSOConstants.Notification.EXCEPTION_STATUS,
              SAMLSSOConstants.Notification.EXCEPTION_MESSAGE,
              reqValidationDTO.getAssertionConsumerURL(),
              req,
              resp);
          return;
        } else {
          throw new IdentityException("Session data is not found for authenticated user");
        }
      }
    } else {
      populateAuthnReqDTO(req, authnReqDTO, sessionDTO, authResult);
      req.setAttribute(SAMLSSOConstants.AUTHENTICATION_RESULT, authResult);
      String relayState = null;

      if (req.getParameter(SAMLSSOConstants.RELAY_STATE) != null) {
        relayState = req.getParameter(SAMLSSOConstants.RELAY_STATE);
      } else {
        relayState = sessionDTO.getRelayState();
      }

      startTenantFlow(authnReqDTO.getTenantDomain());

      if (sessionId == null) {
        sessionId = UUIDGenerator.generateUUID();
      }

      SAMLSSOService samlSSOService = new SAMLSSOService();
      SAMLSSORespDTO authRespDTO =
          samlSSOService.authenticate(
              authnReqDTO,
              sessionId,
              authResult.isAuthenticated(),
              authResult.getAuthenticatedAuthenticators(),
              SAMLSSOConstants.AuthnModes.USERNAME_PASSWORD);

      if (authRespDTO.isSessionEstablished()) { // authenticated

        storeTokenIdCookie(sessionId, req, resp, authnReqDTO.getTenantDomain());
        removeSessionDataFromCache(
            CharacterEncoder.getSafeText(req.getParameter("sessionDataKey")));

        sendResponse(
            req,
            resp,
            relayState,
            authRespDTO.getRespString(),
            authRespDTO.getAssertionConsumerURL(),
            authRespDTO.getSubject().getAuthenticatedSubjectIdentifier(),
            authResult.getAuthenticatedIdPs(),
            sessionDTO.getTenantDomain());
      } else { // authentication FAILURE
        String errorResp = authRespDTO.getRespString();
        sendNotification(
            errorResp,
            SAMLSSOConstants.Notification.EXCEPTION_STATUS,
            SAMLSSOConstants.Notification.EXCEPTION_MESSAGE,
            authRespDTO.getAssertionConsumerURL(),
            req,
            resp);
      }
    }
  }
  /**
   * Sends the user for authentication to the login page
   *
   * @param req
   * @param resp
   * @param signInRespDTO
   * @param relayState
   * @throws ServletException
   * @throws IOException
   */
  private void sendToFrameworkForAuthentication(
      HttpServletRequest req,
      HttpServletResponse resp,
      SAMLSSOReqValidationResponseDTO signInRespDTO,
      String relayState,
      boolean isPost)
      throws ServletException, IOException, UserStoreException, IdentityException {

    SAMLSSOSessionDTO sessionDTO = new SAMLSSOSessionDTO();
    sessionDTO.setHttpQueryString(req.getQueryString());
    sessionDTO.setDestination(signInRespDTO.getDestination());
    sessionDTO.setRelayState(relayState);
    sessionDTO.setRequestMessageString(signInRespDTO.getRequestMessageString());
    sessionDTO.setIssuer(signInRespDTO.getIssuer());
    sessionDTO.setRequestID(signInRespDTO.getId());
    sessionDTO.setSubject(signInRespDTO.getSubject());
    sessionDTO.setRelyingPartySessionId(signInRespDTO.getRpSessionId());
    sessionDTO.setAssertionConsumerURL(signInRespDTO.getAssertionConsumerURL());
    sessionDTO.setTenantDomain(SAMLSSOUtil.getTenantDomainFromThreadLocal());

    if (sessionDTO.getTenantDomain() == null) {
      String[] splitIssuer = sessionDTO.getIssuer().split("@");
      if (splitIssuer != null
          && splitIssuer.length == 2
          && !splitIssuer[0].trim().isEmpty()
          && !splitIssuer[1].trim().isEmpty()) {
        sessionDTO.setTenantDomain(splitIssuer[1]);
      } else {
        sessionDTO.setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);
      }
    }
    SAMLSSOUtil.setTenantDomainInThreadLocal(sessionDTO.getTenantDomain());

    sessionDTO.setForceAuth(signInRespDTO.isForceAuthn());
    sessionDTO.setPassiveAuth(signInRespDTO.isPassive());
    sessionDTO.setValidationRespDTO(signInRespDTO);
    sessionDTO.setIdPInitSSO(signInRespDTO.isIdPInitSSO());

    String sessionDataKey = UUIDGenerator.generateUUID();
    addSessionDataToCache(
        sessionDataKey,
        sessionDTO,
        IdPManagementUtil.getIdleSessionTimeOut(sessionDTO.getTenantDomain()));

    String commonAuthURL = CarbonUIUtil.getAdminConsoleURL(req);
    commonAuthURL =
        commonAuthURL.replace(
            FrameworkConstants.RequestType.CLAIM_TYPE_SAML_SSO
                + "/"
                + FrameworkConstants.CARBON
                + "/",
            FrameworkConstants.COMMONAUTH);
    String selfPath =
        URLEncoder.encode("/" + FrameworkConstants.RequestType.CLAIM_TYPE_SAML_SSO, "UTF-8");
    // Setting authentication request context
    AuthenticationRequest authenticationRequest = new AuthenticationRequest();

    // Adding query parameters
    authenticationRequest.appendRequestQueryParams(req.getParameterMap());
    for (Enumeration headerNames = req.getHeaderNames(); headerNames.hasMoreElements(); ) {
      String headerName = headerNames.nextElement().toString();
      authenticationRequest.addHeader(headerName, req.getHeader(headerName));
    }

    authenticationRequest.setRelyingParty(signInRespDTO.getIssuer());
    authenticationRequest.setCommonAuthCallerPath(selfPath);
    authenticationRequest.setForceAuth(signInRespDTO.isForceAuthn());
    if (!authenticationRequest.getForceAuth()
        && authenticationRequest.getRequestQueryParam("forceAuth") != null) {
      String[] forceAuth = authenticationRequest.getRequestQueryParam("forceAuth");
      if (!forceAuth[0].trim().isEmpty() && Boolean.parseBoolean(forceAuth[0].trim())) {
        authenticationRequest.setForceAuth(Boolean.parseBoolean(forceAuth[0].trim()));
      }
    }
    authenticationRequest.setPassiveAuth(signInRespDTO.isPassive());
    authenticationRequest.setTenantDomain(sessionDTO.getTenantDomain());
    authenticationRequest.setPost(isPost);

    // Creating cache entry and adding entry to the cache before calling to commonauth
    AuthenticationRequestCacheEntry authRequest =
        new AuthenticationRequestCacheEntry(authenticationRequest);
    FrameworkUtils.addAuthenticationRequestToCache(
        sessionDataKey,
        authRequest,
        IdPManagementUtil.getIdleSessionTimeOut(sessionDTO.getTenantDomain()));
    StringBuilder queryStringBuilder = new StringBuilder();
    queryStringBuilder
        .append(commonAuthURL)
        .append("?")
        .append(SAMLSSOConstants.SESSION_DATA_KEY)
        .append("=")
        .append(sessionDataKey)
        .append("&")
        .append(FrameworkConstants.RequestParams.TYPE)
        .append("=")
        .append(FrameworkConstants.RequestType.CLAIM_TYPE_SAML_SSO);
    FrameworkUtils.setRequestPathCredentials(req);
    resp.sendRedirect(queryStringBuilder.toString());
  }