package com.airlock.iam.oauth2.domain.model.provider;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.api.domain.model.validation.GenericError;
import com.airlock.iam.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.base.api.domain.model.validation.ValidatorError;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.user.CurrentLocale;
import com.airlock.iam.common.api.domain.model.user.UserIdentity;
import com.airlock.iam.common.domain.model.user.PrincipalInfo;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.api.domain.model.redflag.RedFlag;
import com.airlock.iam.flow.api.domain.model.session.FlowSession;
import com.airlock.iam.flow.api.domain.model.step.GenericStepError;
import com.airlock.iam.flow.api.domain.model.step.StepResult;
import com.airlock.iam.flow.api.domain.model.step.StepResultBuilder;
import com.airlock.iam.flow.api.domain.model.step.StepSession;
import com.airlock.iam.flow.domain.model.ValidationStepResultData;
import com.airlock.iam.log.api.signing.SigningLogger;
import com.airlock.iam.log.usertrail.UserTrailLogger;
import com.airlock.iam.oauth2.api.domain.model.AuthorizationServerId;
import com.airlock.iam.oauth2.api.domain.model.OAuth2ClientId;
import com.airlock.iam.oauth2.api.domain.model.OAuth2Scope;
import com.airlock.iam.oauth2.api.domain.model.event.OAuth2ConsentEventFactory;
import com.airlock.iam.oauth2.application.configuration.OAuth2ConsentConfig;
import com.airlock.iam.oauth2.application.configuration.OAuth2ConsentStepConfig;
import com.airlock.iam.oauth2.application.configuration.OAuth2LocalConsentConfig;
import com.airlock.iam.oauth2.application.configuration.OAuth2RemoteConsentConfig;
import com.airlock.iam.oauth2.domain.model.OAuth2Response;
import com.airlock.iam.oauth2.domain.model.client.OAuth2Client;
import com.airlock.iam.oauth2.domain.model.codegrant.OAuth2AuthorizationCodeGrantConsentSessionAttribute;
import com.airlock.iam.oauth2.domain.model.codegrant.OAuth2AuthorizationCodeGrantFlowSessionAttribute;
import com.airlock.iam.oauth2.domain.model.codegrant.OAuth2OidcAuthorizationRequestAttributes;
import com.airlock.iam.oauth2.domain.model.consent.OAuth2ConsentStatus;
import com.airlock.iam.oauth2.domain.model.remoteconsent.RemoteConsentEncoder;
import com.airlock.iam.oauth2.domain.model.remoteconsent.RemoteConsentVerificationResult;
import com.airlock.iam.oauth2.domain.model.response.OAuth2ErrorResponseBuilder;
import com.airlock.iam.oauth2.domain.model.validation.OAuth2Error;
import com.airlock.iam.oauth2.domain.service.OAuth2ScopeTranslationService;
import com.airlock.iam.oauth2.domain.service.OpenIdProviderUserAgentStateService;
import com.airlock.iam.oauth2.domain.service.consent.OAuth2ConsentService;
import com.airlock.iam.oauth2.domain.service.consent.OAuth2RemoteConsentService;
import com.google.common.collect.Streams;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@LogicForConfig(OAuth2ConsentStepConfig.class)
public class OAuth2ConsentStepImpl implements OAuth2ConsentStep {
   private static final ELogger LOG = ELoggerFactory.getLogger(OAuth2ConsentStepImpl.class);
   private static final String OAUTH2_CONSENT_ATTRIBUTE = "consent";
   private final Provider<OAuth2AuthorizationCodeGrantFlowSessionAttribute> authorizationCodeGrantFlowSessionAttribute;
   private final Provider<OAuth2ConsentService> localConsentService;
   private final Provider<FlowSession> flowSession;
   private final Provider<RedFlag> consentRequiredRedFlag;
   private final Provider<Optional<OAuth2ConsentConfig>> consentConfig;
   private final Provider<OAuth2ConsentClientProvider> clientProvider;
   private final Provider<CurrentLocale> currentLocale;
   private final Provider<OAuth2ScopeTranslationService> scopeTranslationService;
   private final Provider<OAuth2ConsentStepSession> stepSession;
   private final Provider<OAuth2RemoteConsentService> remoteConsentService;
   private final Provider<OpenIdProviderUserAgentStateService> sessionStateService;
   private final Provider<OAuth2ConsentEventFactory> consentEventFactory;
   private final Provider<SigningLogger> signingLogger;
   private final Provider<PrincipalInfo> principalInfo;
   private final BiConsumer<String, String> userTrailLogger;

   @Inject
   public OAuth2ConsentStepImpl(
      Provider<OAuth2AuthorizationCodeGrantFlowSessionAttribute> authorizationCodeGrantFlowSessionAttribute,
      Provider<OAuth2ConsentService> localConsentService,
      Provider<FlowSession> flowSession,
      Provider<RedFlag> consentRequiredRedFlag,
      Provider<Optional<OAuth2ConsentConfig>> consentConfig,
      Provider<OAuth2ConsentClientProvider> clientProvider,
      Provider<CurrentLocale> currentLocale,
      Provider<OAuth2ConsentStepSession> stepSession,
      Provider<OAuth2RemoteConsentService> remoteConsentService,
      Provider<OpenIdProviderUserAgentStateService> sessionStateService,
      Provider<SigningLogger> signingLogger,
      Provider<PrincipalInfo> principalInfo,
      Provider<OAuth2ScopeTranslationService> scopeTranslationService,
      Provider<OAuth2ConsentEventFactory> consentEventFactory
   ) {
      this(
         authorizationCodeGrantFlowSessionAttribute,
         localConsentService,
         flowSession,
         consentRequiredRedFlag,
         consentConfig,
         clientProvider,
         currentLocale,
         scopeTranslationService,
         stepSession,
         remoteConsentService,
         sessionStateService,
         consentEventFactory,
         signingLogger,
         principalInfo,
         UserTrailLogger::log
      );
   }

   private OAuth2ConsentStepImpl(
      Provider<OAuth2AuthorizationCodeGrantFlowSessionAttribute> authorizationCodeGrantFlowSessionAttribute,
      Provider<OAuth2ConsentService> localConsentService,
      Provider<FlowSession> flowSession,
      Provider<RedFlag> consentRequiredRedFlag,
      Provider<Optional<OAuth2ConsentConfig>> consentConfig,
      Provider<OAuth2ConsentClientProvider> clientProvider,
      Provider<CurrentLocale> currentLocale,
      Provider<OAuth2ScopeTranslationService> scopeTranslationService,
      Provider<OAuth2ConsentStepSession> stepSession,
      Provider<OAuth2RemoteConsentService> remoteConsentService,
      Provider<OpenIdProviderUserAgentStateService> sessionStateService,
      Provider<OAuth2ConsentEventFactory> consentEventFactory,
      Provider<SigningLogger> signingLogger,
      Provider<PrincipalInfo> principalInfo,
      BiConsumer<String, String> userTrailLogger
   ) {
      this.authorizationCodeGrantFlowSessionAttribute = authorizationCodeGrantFlowSessionAttribute;
      this.localConsentService = localConsentService;
      this.flowSession = flowSession;
      this.consentRequiredRedFlag = consentRequiredRedFlag;
      this.consentConfig = consentConfig;
      this.clientProvider = clientProvider;
      this.currentLocale = currentLocale;
      this.scopeTranslationService = scopeTranslationService;
      this.stepSession = stepSession;
      this.remoteConsentService = remoteConsentService;
      this.sessionStateService = sessionStateService;
      this.consentEventFactory = consentEventFactory;
      this.signingLogger = signingLogger;
      this.principalInfo = principalInfo;
      this.userTrailLogger = userTrailLogger;
   }

   @Override
   public Optional<StepSession> createSession() {
      return Optional.of(new OAuth2ConsentStepSession());
   }

   @Override
   public StepResult initialize() {
      List<OAuth2Scope> requestedScopes = ((OAuth2AuthorizationCodeGrantFlowSessionAttribute)this.authorizationCodeGrantFlowSessionAttribute.get()).scopes();
      OAuth2ConsentService.PreProcessConsentResult result = ((OAuth2ConsentService)this.localConsentService.get()).preProcess(requestedScopes);
      if (result.getError().isPresent()) {
         LOG.info("None of the requested scopes can be granted and empty scopes are not allowed.");
         return this.authorizationFailedWithError(OAuth2Error.ACCESS_DENIED);
      } else {
         List<OAuth2Scope> grantableScopes = result.getGrantableScopes().orElseThrow();
         Set<OAuth2Scope> alwaysGrantedScopes = result.getAlwaysGrantedScopes().orElseThrow();
         if (((Optional)this.consentConfig.get()).filter(OAuth2LocalConsentConfig.class::isInstance).isPresent()) {
            return this.handleLocalConsent(grantableScopes, alwaysGrantedScopes);
         } else if (((Optional)this.consentConfig.get()).filter(OAuth2RemoteConsentConfig.class::isInstance).isPresent()) {
            return this.handleRemoteConsent(grantableScopes);
         } else if (((Optional)this.consentConfig.get()).isEmpty()) {
            return this.handleNoConsent(grantableScopes, alwaysGrantedScopes);
         } else {
            throw new FailureException(
               "Unknown consent config: " + ((OAuth2ConsentConfig)((Optional)this.consentConfig.get()).get()).getClass().getSimpleName()
            );
         }
      }
   }

   @Override
   public StepResult retrieveLocalConsent() {
      Optional<OAuth2LocalConsentAttribute> localConsentAttribute = this.localConsentAttributeFromSession();
      return localConsentAttribute.isEmpty()
         ? this.stepResultFactory().failDueToUnexpectedCall("No local consent in progress").build()
         : this.stepResultFactory()
            .stay(OAuth2ConsentStep.OAuth2ConsentStepActions.OAUTH2_AUTHORIZATION_SERVER_LOCAL_CONSENT_REQUIRED)
            .resultData(localConsentAttribute.get())
            .build();
   }

   @Override
   public StepResult grantLocalConsent(Set<String> grantedScopes) {
      Optional<OAuth2ConsentStepSession.LocalConsentSessionData> localConsentSessionData = ((OAuth2ConsentStepSession)this.stepSession.get())
         .getLocalConsentSessionData();
      if (localConsentSessionData.isEmpty()) {
         return this.stepResultFactory().failDueToUnexpectedCall("No local consent in progress").build();
      }

      OAuth2LocalConsentAttribute localConsentFromSession = localConsentSessionData.get().getLocalConsentWithScopes();
      Optional<OAuth2ConsentService.PreProcessConsentResult.ConsentResultError> error = ((OAuth2ConsentService)this.localConsentService.get())
         .validate(localConsentFromSession.getGrantableScopes(), grantedScopes);
      if (error.isPresent()) {
         return this.stepResultFactory()
            .failRetry(GenericStepError.VALIDATION_FAILED, OAuth2ConsentStep.OAuth2ConsentStepActions.OAUTH2_AUTHORIZATION_SERVER_LOCAL_CONSENT_REQUIRED)
            .resultData(new ValidationStepResultData(this.mapError(error.get())))
            .addAdditionalAttribute("consent", localConsentFromSession)
            .build();
      }

      Set<String> deniedScopes = this.getDeniedScopes(localConsentFromSession.getGrantableScopes(), grantedScopes);
      if (this.isStorageFlagEnabled()) {
         ((OAuth2ConsentService)this.localConsentService.get()).saveConsentResults(grantedScopes, deniedScopes);
      }

      Set<String> alwaysGrantedScopes = localConsentFromSession.getAlwaysGrantedScopes()
         .stream()
         .map(OAuth2ScopeAttribute::getScope)
         .collect(Collectors.toSet());
      return this.handleGrantSuccess(grantedScopes, deniedScopes, alwaysGrantedScopes);
   }

   @Override
   public StepResult denyLocalConsent() {
      LOG.info("The user denied the consent.");
      this.logToUserTrail(
         "User explicitly denied access to OAuth 2.0 / OpenID Connect client "
            + LogUtil.quote(((OAuth2AuthorizationCodeGrantFlowSessionAttribute)this.authorizationCodeGrantFlowSessionAttribute.get()).request().clientId().id())
      );
      return this.authorizationFailedWithError(OAuth2Error.ACCESS_DENIED);
   }

   @Override
   public StepResult checkRemoteConsent(String consentToken) {
      Optional<RemoteConsentVerificationResult> result = ((OAuth2RemoteConsentService)this.remoteConsentService.get()).check(consentToken);
      if (result.isEmpty()) {
         return this.stepResultFactory().failDueToUnexpectedCall("No remote consent check in progress").build();
      }

      RemoteConsentVerificationResult verificationResult = result.get();

      return switch (verificationResult.type()) {
         case CONSENT_GIVEN_WITH_SCOPE -> {
            Set<OAuth2Scope> grantedScopes = verificationResult.grantedScope().orElseThrow();
            this.logToUserTrail(
               "User allowed access to OAuth 2.0 / OpenID Connect client "
                  + LogUtil.quote(
                     ((OAuth2AuthorizationCodeGrantFlowSessionAttribute)this.authorizationCodeGrantFlowSessionAttribute.get()).request().clientId().id()
                  )
                  + ". Granted scopes: "
                  + grantedScopes.stream().map(OAuth2Scope::name).toList()
            );
            this.publishUserConsentEvent(Set.copyOf(grantedScopes), Collections.emptySet(), Collections.emptySet());
            yield this.handleSuccess(grantedScopes);
         }
         case CONSENT_DENIED -> {
            this.logToUserTrail(
               "User explicitly denied access to OAuth 2.0 / OpenID Connect client "
                  + LogUtil.quote(
                     ((OAuth2AuthorizationCodeGrantFlowSessionAttribute)this.authorizationCodeGrantFlowSessionAttribute.get()).request().clientId().id()
                  )
                  + " (through remote consent)"
            );
            yield this.authorizationFailedWithError(OAuth2Error.ACCESS_DENIED);
         }
         case JWT_INVALID -> this.authorizationFailedWithError(OAuth2Error.SERVER_ERROR);
      };
   }

   @Override
   public String getDescription() {
      return "OAuth 2.0 Consent Step";
   }

   private StepResult handleLocalConsent(List<OAuth2Scope> grantableScopes, Set<OAuth2Scope> alwaysGrantedScopes) {
      List<OAuth2ScopeAttribute> grantableScopeAttributes = ((OAuth2ConsentService)this.localConsentService.get()).loadSavedConsents(grantableScopes);
      if (((OAuth2ConsentService)this.localConsentService.get()).isConsentStorageEnabled()
         && this.areAllScopesDecided(grantableScopeAttributes)
         && this.isStorageFlagEnabled()) {
         Set<String> grantedScopes = this.filterScopes(grantableScopeAttributes, OAuth2ConsentStatus.GRANTED);
         Set<String> deniedScopes = this.filterScopes(grantableScopeAttributes, OAuth2ConsentStatus.DENIED);
         return this.handleAutomaticGrantSuccess(grantedScopes, deniedScopes, alwaysGrantedScopes);
      } else {
         Set<OAuth2ScopeAttribute> alwaysGrantedScopeAttributes = alwaysGrantedScopes.stream()
            .map(scope -> OAuth2ScopeAttribute.oAuth2ScopeAttribute(scope.name()))
            .collect(Collectors.toSet());
         OAuth2LocalConsentAttribute localConsentAttribute = new OAuth2LocalConsentAttribute(
            this.getClientName(), grantableScopeAttributes, alwaysGrantedScopeAttributes
         );
         ((OAuth2ConsentStepSession)this.stepSession.get())
            .setLocalConsentSessionData(new OAuth2ConsentStepSession.LocalConsentSessionData(localConsentAttribute));
         return this.stepResultFactory()
            .stay(OAuth2ConsentStep.OAuth2ConsentStepActions.OAUTH2_AUTHORIZATION_SERVER_LOCAL_CONSENT_REQUIRED)
            .addAdditionalAttribute("consent", localConsentAttribute)
            .build();
      }
   }

   private boolean areAllScopesDecided(List<OAuth2ScopeAttribute> scopes) {
      return scopes.stream().map(OAuth2ScopeAttribute::getConsentStatus).noneMatch(OAuth2ConsentStatus.UNDECIDED::equals);
   }

   private StepResult handleRemoteConsent(List<OAuth2Scope> requestedScopes) {
      OAuth2RemoteConsentConfig remoteConsentConfig = (OAuth2RemoteConsentConfig)((Optional)this.consentConfig.get()).orElseThrow();
      RemoteConsentEncoder.JwtEncoderResult consentTokenJwt = ((OAuth2RemoteConsentService)this.remoteConsentService.get()).createConsentToken(requestedScopes);
      OAuth2RemoteConsentAttribute consentAttribute = new OAuth2RemoteConsentAttribute(remoteConsentConfig.getRemoteConsentUrl(), consentTokenJwt.jwt());
      return this.stepResultFactory()
         .stay(OAuth2ConsentStep.OAuth2ConsentStepActions.OAUTH2_AUTHORIZATION_SERVER_REMOTE_CONSENT_REQUIRED)
         .addAdditionalAttribute("consent", consentAttribute)
         .build();
   }

   private StepResult handleNoConsent(List<OAuth2Scope> grantedScopes, Set<OAuth2Scope> alwaysGrantedScopes) {
      this.publishUserConsentEvent(Set.copyOf(grantedScopes), Collections.emptySet(), Set.copyOf(alwaysGrantedScopes));
      Set<OAuth2Scope> allScopes = new HashSet<>(grantedScopes);
      allScopes.addAll(alwaysGrantedScopes);
      return this.handleSuccess(Set.copyOf(allScopes));
   }

   private Optional<OAuth2LocalConsentAttribute> localConsentAttributeFromSession() {
      return ((OAuth2ConsentStepSession)this.stepSession.get())
         .getLocalConsentSessionData()
         .map(OAuth2ConsentStepSession.LocalConsentSessionData::getLocalConsentWithScopes)
         .map(this::translateScopes);
   }

   private OAuth2LocalConsentAttribute translateScopes(OAuth2LocalConsentAttribute localConsentAttribute) {
      return new OAuth2LocalConsentAttribute(
         this.getClientName(),
         ((OAuth2ScopeTranslationService)this.scopeTranslationService.get())
            .translateScopeNames(
               ((OAuth2AuthorizationCodeGrantFlowSessionAttribute)this.authorizationCodeGrantFlowSessionAttribute.get()).authorizationServerId(),
               localConsentAttribute.getGrantableScopes()
            ),
         localConsentAttribute.getAlwaysGrantedScopes()
      );
   }

   private ValidationResult mapError(OAuth2ConsentService.PreProcessConsentResult.ConsentResultError consentResultError) {
      if (consentResultError.equals(OAuth2ConsentService.PreProcessConsentResult.ConsentResultError.EMTPY_SCOPE_NOT_ALLOWED)) {
         return ValidationResult.invalid(ValidatorError.validatorError("scopes", GenericError.REQUIRED));
      } else if (consentResultError.equals(OAuth2ConsentService.PreProcessConsentResult.ConsentResultError.INVALID_SCOPE)) {
         return ValidationResult.invalid(ValidatorError.validatorError("scopes", GenericError.INVALID_VALUE));
      } else {
         throw new IllegalStateException("Can not map unknown error result: " + consentResultError);
      }
   }

   private String getClientName() {
      OAuth2Client oAuth2Client = ((OAuth2ConsentClientProvider)this.clientProvider.get()).provide();
      return oAuth2Client.clientName().getValue(((CurrentLocale)this.currentLocale.get()).get(), key -> key).orElse(oAuth2Client.clientId().id());
   }

   private StepResult handleGrantSuccess(Set<String> grantedScopes, Set<String> deniedScopes, Set<String> alwaysGrantedScopes) {
      Set<OAuth2Scope> grantedUserScopes = grantedScopes.stream().map(OAuth2Scope::of).collect(Collectors.toSet());
      Set<OAuth2Scope> deniedUserScopes = deniedScopes.stream().map(OAuth2Scope::of).collect(Collectors.toSet());
      Set<OAuth2Scope> alwaysGrantedUserScopes = alwaysGrantedScopes.stream().map(OAuth2Scope::of).collect(Collectors.toSet());
      this.publishUserConsentEvent(grantedUserScopes, deniedUserScopes, alwaysGrantedUserScopes);
      this.logToUserTrail(
         "User allowed access to OAuth 2.0 / OpenID Connect client "
            + LogUtil.quote(((OAuth2AuthorizationCodeGrantFlowSessionAttribute)this.authorizationCodeGrantFlowSessionAttribute.get()).request().clientId().id())
            + ". Granted scopes: "
            + grantedScopes
            + ". Denied scopes: "
            + deniedScopes
            + ". Scopes always granted by AS: "
            + alwaysGrantedScopes
      );
      Set<String> allScopes = new HashSet<>(grantedScopes);
      allScopes.addAll(alwaysGrantedScopes);
      return this.handleSuccess(OAuth2Scope.convertToScopes(allScopes));
   }

   private void publishUserConsentEvent(Set<OAuth2Scope> grantedScopes, Set<OAuth2Scope> deniedScopes, Set<OAuth2Scope> alwaysGrantedScopes) {
      UserId userId = ((PrincipalInfo)this.principalInfo.get()).getKnownIdentity().map(UserIdentity::id).orElseThrow();
      AuthorizationServerId authorizationServerId = ((OAuth2AuthorizationCodeGrantFlowSessionAttribute)this.authorizationCodeGrantFlowSessionAttribute.get())
         .authorizationServerId();
      OAuth2ClientId oAuth2ClientId = ((OAuth2AuthorizationCodeGrantFlowSessionAttribute)this.authorizationCodeGrantFlowSessionAttribute.get())
         .request()
         .clientId();
      ((OAuth2ConsentEventFactory)this.consentEventFactory.get())
         .publishUserConsentEvent(userId, grantedScopes, deniedScopes, alwaysGrantedScopes, oAuth2ClientId, authorizationServerId);
   }

   private StepResult handleAutomaticGrantSuccess(Set<String> grantedScopes, Set<String> deniedScopes, Set<OAuth2Scope> alwaysGrantedScopes) {
      this.logToUserTrail(
         "Access to OAuth 2.0 / OpenID Connect client "
            + LogUtil.quote(((OAuth2AuthorizationCodeGrantFlowSessionAttribute)this.authorizationCodeGrantFlowSessionAttribute.get()).request().clientId().id())
            + " was granted automatically based on previously granted/denied consents. Automatically granted scopes: "
            + grantedScopes
            + ". Automatically denied scopes: "
            + deniedScopes
            + ". Scopes always granted by AS: "
            + alwaysGrantedScopes.stream().map(OAuth2Scope::name).collect(Collectors.toSet())
      );
      Set<String> allScopes = Streams.concat(new Stream[]{grantedScopes.stream(), alwaysGrantedScopes.stream().map(OAuth2Scope::name)})
         .collect(Collectors.toSet());
      return this.handleSuccess(OAuth2Scope.convertToScopes(allScopes));
   }

   private StepResult handleSuccess(Set<OAuth2Scope> grantedScopes) {
      ((FlowSession)this.flowSession.get()).flowAttributes().putOrMergeAttribute(new OAuth2AuthorizationCodeGrantConsentSessionAttribute(grantedScopes));
      ((FlowSession)this.flowSession.get()).takeDownRedFlag((RedFlag)this.consentRequiredRedFlag.get());
      return this.stepResultFactory().success().build();
   }

   private Set<String> filterScopes(List<OAuth2ScopeAttribute> scopes, OAuth2ConsentStatus status) {
      return scopes.stream().filter(scope -> status.equals(scope.getConsentStatus())).map(OAuth2ScopeAttribute::getScope).collect(Collectors.toSet());
   }

   private Set<String> getDeniedScopes(List<OAuth2ScopeAttribute> grantableScopes, Set<String> grantedScopes) {
      return grantableScopes.stream().map(OAuth2ScopeAttribute::getScope).filter(Predicate.not(grantedScopes::contains)).collect(Collectors.toSet());
   }

   private StepResult authorizationFailedWithError(OAuth2Error oAuth2Error) {
      OAuth2OidcAuthorizationRequestAttributes request = ((OAuth2AuthorizationCodeGrantFlowSessionAttribute)this.authorizationCodeGrantFlowSessionAttribute
            .get())
         .request();
      OAuth2ErrorResponseBuilder errorResponseBuilder = OAuth2ErrorResponseBuilder.errorResponseBuilder(
         ((OpenIdProviderUserAgentStateService)this.sessionStateService.get()).state(),
         request.clientId(),
         request.effectiveRedirectUri(),
         request.state(),
         request.responseMode()
      );
      ((SigningLogger)this.signingLogger.get()).log("OAuth 2.0 authorization failed in consent step with error: " + oAuth2Error.getParameterValue());
      OAuth2Response response = errorResponseBuilder.buildResponse(oAuth2Error);
      StepResultBuilder stepResultBuilder = this.stepResultFactory()
         .fail(OAuth2ConsentStep.OAuth2ConsentStepErrors.OAUTH2_AUTHORIZATION_SERVER_AUTHORIZATION_FAILED)
         .addAdditionalAttribute("clientRedirectUri", response.redirectUri());
      response.formPostParams().ifPresent(params -> stepResultBuilder.addAdditionalAttribute("formPostParameters", params));
      return stepResultBuilder.build();
   }

   private void logToUserTrail(String message) {
      this.userTrailLogger
         .accept(((PrincipalInfo)this.principalInfo.get()).getKnownIdentity().map(identity -> identity.id().getValue()).orElseThrow(), message);
   }

   private Boolean isStorageFlagEnabled() {
      return ((Optional)this.consentConfig.get()).map(OAuth2ConsentConfig::isEnableConsentStorage).orElse(false);
   }

   @SuppressFBWarnings(justification = "generated code")
   public static OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder builder() {
      return new OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2ConsentStepImplBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Provider<OAuth2AuthorizationCodeGrantFlowSessionAttribute> authorizationCodeGrantFlowSessionAttribute;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<OAuth2ConsentService> localConsentService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<FlowSession> flowSession;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<RedFlag> consentRequiredRedFlag;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<Optional<OAuth2ConsentConfig>> consentConfig;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<OAuth2ConsentClientProvider> clientProvider;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<CurrentLocale> currentLocale;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<OAuth2ScopeTranslationService> scopeTranslationService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<OAuth2ConsentStepSession> stepSession;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<OAuth2RemoteConsentService> remoteConsentService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<OpenIdProviderUserAgentStateService> sessionStateService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<OAuth2ConsentEventFactory> consentEventFactory;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<SigningLogger> signingLogger;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<PrincipalInfo> principalInfo;
      @SuppressFBWarnings(justification = "generated code")
      private BiConsumer<String, String> userTrailLogger;

      @SuppressFBWarnings(justification = "generated code")
      OAuth2ConsentStepImplBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder authorizationCodeGrantFlowSessionAttribute(
         Provider<OAuth2AuthorizationCodeGrantFlowSessionAttribute> authorizationCodeGrantFlowSessionAttribute
      ) {
         this.authorizationCodeGrantFlowSessionAttribute = authorizationCodeGrantFlowSessionAttribute;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder localConsentService(Provider<OAuth2ConsentService> localConsentService) {
         this.localConsentService = localConsentService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder flowSession(Provider<FlowSession> flowSession) {
         this.flowSession = flowSession;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder consentRequiredRedFlag(Provider<RedFlag> consentRequiredRedFlag) {
         this.consentRequiredRedFlag = consentRequiredRedFlag;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder consentConfig(Provider<Optional<OAuth2ConsentConfig>> consentConfig) {
         this.consentConfig = consentConfig;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder clientProvider(Provider<OAuth2ConsentClientProvider> clientProvider) {
         this.clientProvider = clientProvider;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder currentLocale(Provider<CurrentLocale> currentLocale) {
         this.currentLocale = currentLocale;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder scopeTranslationService(Provider<OAuth2ScopeTranslationService> scopeTranslationService) {
         this.scopeTranslationService = scopeTranslationService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder stepSession(Provider<OAuth2ConsentStepSession> stepSession) {
         this.stepSession = stepSession;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder remoteConsentService(Provider<OAuth2RemoteConsentService> remoteConsentService) {
         this.remoteConsentService = remoteConsentService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder sessionStateService(Provider<OpenIdProviderUserAgentStateService> sessionStateService) {
         this.sessionStateService = sessionStateService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder consentEventFactory(Provider<OAuth2ConsentEventFactory> consentEventFactory) {
         this.consentEventFactory = consentEventFactory;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder signingLogger(Provider<SigningLogger> signingLogger) {
         this.signingLogger = signingLogger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder principalInfo(Provider<PrincipalInfo> principalInfo) {
         this.principalInfo = principalInfo;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder userTrailLogger(BiConsumer<String, String> userTrailLogger) {
         this.userTrailLogger = userTrailLogger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ConsentStepImpl build() {
         return new OAuth2ConsentStepImpl(
            this.authorizationCodeGrantFlowSessionAttribute,
            this.localConsentService,
            this.flowSession,
            this.consentRequiredRedFlag,
            this.consentConfig,
            this.clientProvider,
            this.currentLocale,
            this.scopeTranslationService,
            this.stepSession,
            this.remoteConsentService,
            this.sessionStateService,
            this.consentEventFactory,
            this.signingLogger,
            this.principalInfo,
            this.userTrailLogger
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2ConsentStepImpl.OAuth2ConsentStepImplBuilder(authorizationCodeGrantFlowSessionAttribute="
            + this.authorizationCodeGrantFlowSessionAttribute
            + ", localConsentService="
            + this.localConsentService
            + ", flowSession="
            + this.flowSession
            + ", consentRequiredRedFlag="
            + this.consentRequiredRedFlag
            + ", consentConfig="
            + this.consentConfig
            + ", clientProvider="
            + this.clientProvider
            + ", currentLocale="
            + this.currentLocale
            + ", scopeTranslationService="
            + this.scopeTranslationService
            + ", stepSession="
            + this.stepSession
            + ", remoteConsentService="
            + this.remoteConsentService
            + ", sessionStateService="
            + this.sessionStateService
            + ", consentEventFactory="
            + this.consentEventFactory
            + ", signingLogger="
            + this.signingLogger
            + ", principalInfo="
            + this.principalInfo
            + ", userTrailLogger="
            + this.userTrailLogger
            + ")";
      }
   }
}
