package com.airlock.iam.oauth2.domain.service.codegrant;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.common.api.domain.model.claims.Claim;
import com.airlock.iam.common.api.domain.model.persistency.exception.PersisterException;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.oauth2.api.domain.model.AuthorizationServerId;
import com.airlock.iam.oauth2.api.domain.model.OAuth2Scope;
import com.airlock.iam.oauth2.api.domain.model.claims.OAuth2TokenInformation;
import com.airlock.iam.oauth2.api.domain.model.clientauthentication.OAuth2ClientAuthentication;
import com.airlock.iam.oauth2.domain.model.OAuth2Response;
import com.airlock.iam.oauth2.domain.model.client.OAuth2Client;
import com.airlock.iam.oauth2.domain.model.client.OAuth2ClientLoader;
import com.airlock.iam.oauth2.domain.model.clientauthentication.NoClientAuthenticationPayload;
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.codegrant.OpenIdConnectAuthenticationRequestAttributes;
import com.airlock.iam.oauth2.domain.model.logging.OAuth2LogTypes;
import com.airlock.iam.oauth2.domain.model.oidc.OidcUserAgentSessionId;
import com.airlock.iam.oauth2.domain.model.oidc.OidcUserAgentSessions;
import com.airlock.iam.oauth2.domain.model.response.OAuth2ErrorResponseBuilder;
import com.airlock.iam.oauth2.domain.model.session.OAuth2ExtendedSession;
import com.airlock.iam.oauth2.domain.model.session.OAuth2SessionClaims;
import com.airlock.iam.oauth2.domain.model.session.OAuth2SessionId;
import com.airlock.iam.oauth2.domain.model.session.OpenIDProviderUserAgentState;
import com.airlock.iam.oauth2.domain.model.settings.OAuth2OidcAuthorizationCodeGrantSettings;
import com.airlock.iam.oauth2.domain.model.token.AccessTokenInfo;
import com.airlock.iam.oauth2.domain.model.token.AuthorizationCodeInfo;
import com.airlock.iam.oauth2.domain.model.token.OAuth2RawAccessTokenContainer;
import com.airlock.iam.oauth2.domain.model.token.OpenIdConnectIdTokenData;
import com.airlock.iam.oauth2.domain.model.token.RawOAuth2TokenWithHash;
import com.airlock.iam.oauth2.domain.model.token.SessionInfo;
import com.airlock.iam.oauth2.domain.model.validation.OAuth2Error;
import com.airlock.iam.oauth2.domain.service.GrantedScopeProcessorsService;
import com.airlock.iam.oauth2.domain.service.OAuth2AccessTokenClaimsService;
import com.airlock.iam.oauth2.domain.service.OAuth2GrantsSessionService;
import com.airlock.iam.oauth2.domain.service.OpenIdConnectCodeFlowIdTokenClaimsService;
import com.airlock.iam.oauth2.domain.service.persistence.OAuth2AccessTokenGenerator;
import com.airlock.iam.oauth2.domain.service.persistence.OAuth2AuthorizationCodeGenerator;
import com.airlock.iam.plugin.api.config.LogTypes;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Provider;
import java.net.URI;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import lombok.NonNull;

public class OAuth2AuthorizationCodeService {
   @NonNull
   private final ELogger logger;
   @NonNull
   private final AuthorizationServerId authorizationServerId;
   @NonNull
   private final OAuth2ClientLoader clientLoader;
   @NonNull
   private final OAuth2GrantsSessionService sessionService;
   @NonNull
   private final OAuth2AuthorizationCodeGenerator authorizationCodeGenerator;
   @NonNull
   private final OAuth2OidcCodeGrantRedirectUriGenerator codeGrantUriGenerator;
   @NonNull
   private final OAuth2OidcAuthorizationCodeGrantSettings settings;
   @NonNull
   private final Provider<Optional<OpenIDProviderUserAgentState>> sessionStateService;
   @NonNull
   private final Provider<CodeFlowOpenIdConnectIdTokenGenerator> codeFlowIdTokenGenerator;
   @NonNull
   private final Provider<HybridFlowOpenIdConnectIdTokenDataGenerator> hybridFlowIdTokenDataGenerator;
   @NonNull
   private final Provider<OAuth2AccessTokenGenerator> hybridFlowAccessTokenDataGenerator;
   @NonNull
   private final Provider<GrantedScopeProcessorsService> grantedScopeProcessorsService;
   @NonNull
   private final Provider<OAuth2AccessTokenClaimsService> codeFlowAccessTokenClaimsService;
   @NonNull
   private final Provider<OpenIdConnectCodeFlowIdTokenClaimsService> codeFlowIdTokenClaimsService;
   @NonNull
   private final Provider<UserAgentSessionService> userAgentSessionService;
   private final boolean usePersistedClaims;

   public OAuth2Response finishAuthorizationCodeGrant(
      OAuth2AuthorizationCodeGrantFlowSessionAttribute sessionAttribute, UserId userId, Set<OAuth2Scope> grantedScopes, Date userAuthenticationDate
   ) {
      OAuth2OidcAuthorizationRequestAttributes request = sessionAttribute.request();
      OAuth2Client client = this.clientLoader.loadClient(request.clientId());
      OAuth2SessionId sessionId = OAuth2SessionId.generateRandomOAuth2SessionId();
      OAuth2ExtendedSession session = OAuth2ExtendedSession.builder()
         .id(sessionId)
         .clientId(request.clientId())
         .scopes(grantedScopes)
         .authenticationTimestamp(userAuthenticationDate.toInstant())
         .authorizationServerId(this.authorizationServerId)
         .claims(this.usePersistedClaims ? this.evaluateSessionClaims(userId, grantedScopes, request, sessionId) : OAuth2SessionClaims.noClaims())
         .build();
      this.sessionService.createOAuth2Session(session);
      SessionInfo sessionInfo = new SessionInfo(sessionId, userId.getValue(), request.clientId(), this.authorizationServerId, userAuthenticationDate);
      if (!this.settings.isAllowEmptyScope() && grantedScopes.isEmpty()) {
         this.logger
            .warn(
               LogTypes.PLUGIN_INFO,
               "OAuth2: Resulting scope is empty (possibly no scope consent step was configured?) and this is not allowed by the current settings. Cannot issue Authorization Code."
            );
         return OAuth2ErrorResponseBuilder.errorResponseBuilder(
               (Optional<OpenIDProviderUserAgentState>)this.sessionStateService.get(),
               request.clientId(),
               request.effectiveRedirectUri(),
               request.state(),
               request.responseMode()
            )
            .buildResponse(OAuth2Error.SERVER_ERROR);
      }

      OAuth2AuthorizationCode authorizationCode = this.generateAuthorizationCode(request, grantedScopes, sessionInfo);
      OpenIdConnectHybridFlowTokens.OpenIdConnectHybridFlowTokensBuilder hybridFlowTokens = OpenIdConnectHybridFlowTokens.builder();
      if (request instanceof OpenIdConnectAuthenticationRequestAttributes oidcRequest) {
         OidcUserAgentSessions oidcUserAgentSessions = ((UserAgentSessionService)this.userAgentSessionService.get())
            .addOidcUserAgentSession(sessionAttribute.authorizationServerId(), request.clientId(), sessionId, userId);
         OidcUserAgentSessionId oidcUserAgentSessionId = oidcUserAgentSessions.userAgentSessionId();
         ((CodeFlowOpenIdConnectIdTokenGenerator)this.codeFlowIdTokenGenerator.get())
            .generateAndAssignIdToken(oidcRequest, sessionInfo, grantedScopes, Optional.of(oidcUserAgentSessionId));
         if (request.responseType().impliesHybridFlow()) {
            Set<OAuth2Scope> processedScopes = ((GrantedScopeProcessorsService)this.grantedScopeProcessorsService.get())
               .processScopes(grantedScopes, client, OAuth2ClientAuthentication.clientAuthentication(NoClientAuthenticationPayload.NO_AUTHENTICATION_PAYLOAD));
            if (request.responseType().requestsIdToken()) {
               hybridFlowTokens.idToken(this.generateIdTokenData(processedScopes, sessionInfo, oidcRequest, oidcUserAgentSessionId));
            }

            if (request.responseType().requestsAccessToken()) {
               hybridFlowTokens.accessToken(this.generateAccessToken(processedScopes, sessionInfo));
            }
         }
      } else {
         ((UserAgentSessionService)this.userAgentSessionService.get()).addOAuth2UserAgentSession(sessionAttribute.authorizationServerId(), sessionId);
      }

      return this.codeGrantUriGenerator.codeGrantUri(request, authorizationCode, grantedScopes, sessionInfo, client, hybridFlowTokens.build()).buildResponse();
   }

   private OAuth2SessionClaims evaluateSessionClaims(
      UserId userId, Set<OAuth2Scope> grantedScopes, OAuth2OidcAuthorizationRequestAttributes request, OAuth2SessionId sessionId
   ) {
      OAuth2TokenInformation tokenInformation = new OAuth2TokenInformation(userId.getValue(), grantedScopes, request.clientId(), sessionId);
      List<Claim<?>> codeFlowIdTokenClaims = request.isOpenIdConnect()
         ? ((OpenIdConnectCodeFlowIdTokenClaimsService)this.codeFlowIdTokenClaimsService.get()).evaluateIdTokenClaims(tokenInformation)
         : List.of();
      List<Claim<?>> codeFlowAccessTokenClaims = ((OAuth2AccessTokenClaimsService)this.codeFlowAccessTokenClaimsService.get())
         .evaluateAccessTokenClaims(tokenInformation);
      return OAuth2SessionClaims.sessionClaims(codeFlowIdTokenClaims, codeFlowAccessTokenClaims);
   }

   private OpenIdConnectIdTokenData generateIdTokenData(
      Set<OAuth2Scope> grantedScopes,
      SessionInfo sessionInfo,
      OpenIdConnectAuthenticationRequestAttributes oidcRequest,
      OidcUserAgentSessionId oidcUserAgentSessionId
   ) {
      return ((HybridFlowOpenIdConnectIdTokenDataGenerator)this.hybridFlowIdTokenDataGenerator.get())
         .generateIdTokenData(oidcRequest, sessionInfo, grantedScopes, oidcUserAgentSessionId);
   }

   private OAuth2RawAccessTokenContainer generateAccessToken(Set<OAuth2Scope> grantedScopes, SessionInfo sessionInfo) {
      try {
         return ((OAuth2AccessTokenGenerator)this.hybridFlowAccessTokenDataGenerator.get())
            .generateAndAssignAccessToken(sessionInfo, new AccessTokenInfo(grantedScopes, Optional.empty()));
      } catch (PersisterException e) {
         this.logger.warn(OAuth2LogTypes.OAUTH2_MESSAGE, "Error generating hybrid flow access token: " + e.getMessage(), e);
         throw new FailureException("Error generating hybrid flow access token: " + e.getMessage());
      }
   }

   private OAuth2AuthorizationCode generateAuthorizationCode(
      OAuth2OidcAuthorizationRequestAttributes request, Set<OAuth2Scope> grantedScopes, SessionInfo sessionInfo
   ) {
      try {
         AuthorizationCodeInfo authCodeInfo = new AuthorizationCodeInfo(
            grantedScopes, request.requestedRedirectUri().map(URI::toASCIIString), request.pkceChallenge()
         );
         RawOAuth2TokenWithHash rawOAuth2TokenWithHash = this.authorizationCodeGenerator.generateAndAssignAuthzCodeToken(authCodeInfo, sessionInfo);
         return new OAuth2AuthorizationCode(rawOAuth2TokenWithHash.toTokenString());
      } catch (PersisterException e) {
         this.logger.warn(OAuth2LogTypes.OAUTH2_MESSAGE, "Error generating authorization code: " + e.getMessage(), e);
         throw new FailureException("Error generating authorization code: " + e.getMessage());
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   OAuth2AuthorizationCodeService(
      @NonNull ELogger logger,
      @NonNull AuthorizationServerId authorizationServerId,
      @NonNull OAuth2ClientLoader clientLoader,
      @NonNull OAuth2GrantsSessionService sessionService,
      @NonNull OAuth2AuthorizationCodeGenerator authorizationCodeGenerator,
      @NonNull OAuth2OidcCodeGrantRedirectUriGenerator codeGrantUriGenerator,
      @NonNull OAuth2OidcAuthorizationCodeGrantSettings settings,
      @NonNull Provider<Optional<OpenIDProviderUserAgentState>> sessionStateService,
      @NonNull Provider<CodeFlowOpenIdConnectIdTokenGenerator> codeFlowIdTokenGenerator,
      @NonNull Provider<HybridFlowOpenIdConnectIdTokenDataGenerator> hybridFlowIdTokenDataGenerator,
      @NonNull Provider<OAuth2AccessTokenGenerator> hybridFlowAccessTokenDataGenerator,
      @NonNull Provider<GrantedScopeProcessorsService> grantedScopeProcessorsService,
      @NonNull Provider<OAuth2AccessTokenClaimsService> codeFlowAccessTokenClaimsService,
      @NonNull Provider<OpenIdConnectCodeFlowIdTokenClaimsService> codeFlowIdTokenClaimsService,
      @NonNull Provider<UserAgentSessionService> userAgentSessionService,
      boolean usePersistedClaims
   ) {
      if (logger == null) {
         throw new NullPointerException("logger is marked non-null but is null");
      }

      if (authorizationServerId == null) {
         throw new NullPointerException("authorizationServerId is marked non-null but is null");
      }

      if (clientLoader == null) {
         throw new NullPointerException("clientLoader is marked non-null but is null");
      }

      if (sessionService == null) {
         throw new NullPointerException("sessionService is marked non-null but is null");
      }

      if (authorizationCodeGenerator == null) {
         throw new NullPointerException("authorizationCodeGenerator is marked non-null but is null");
      }

      if (codeGrantUriGenerator == null) {
         throw new NullPointerException("codeGrantUriGenerator is marked non-null but is null");
      }

      if (settings == null) {
         throw new NullPointerException("settings is marked non-null but is null");
      }

      if (sessionStateService == null) {
         throw new NullPointerException("sessionStateService is marked non-null but is null");
      }

      if (codeFlowIdTokenGenerator == null) {
         throw new NullPointerException("codeFlowIdTokenGenerator is marked non-null but is null");
      }

      if (hybridFlowIdTokenDataGenerator == null) {
         throw new NullPointerException("hybridFlowIdTokenDataGenerator is marked non-null but is null");
      }

      if (hybridFlowAccessTokenDataGenerator == null) {
         throw new NullPointerException("hybridFlowAccessTokenDataGenerator is marked non-null but is null");
      }

      if (grantedScopeProcessorsService == null) {
         throw new NullPointerException("grantedScopeProcessorsService is marked non-null but is null");
      }

      if (codeFlowAccessTokenClaimsService == null) {
         throw new NullPointerException("codeFlowAccessTokenClaimsService is marked non-null but is null");
      }

      if (codeFlowIdTokenClaimsService == null) {
         throw new NullPointerException("codeFlowIdTokenClaimsService is marked non-null but is null");
      }

      if (userAgentSessionService == null) {
         throw new NullPointerException("userAgentSessionService is marked non-null but is null");
      }

      this.logger = logger;
      this.authorizationServerId = authorizationServerId;
      this.clientLoader = clientLoader;
      this.sessionService = sessionService;
      this.authorizationCodeGenerator = authorizationCodeGenerator;
      this.codeGrantUriGenerator = codeGrantUriGenerator;
      this.settings = settings;
      this.sessionStateService = sessionStateService;
      this.codeFlowIdTokenGenerator = codeFlowIdTokenGenerator;
      this.hybridFlowIdTokenDataGenerator = hybridFlowIdTokenDataGenerator;
      this.hybridFlowAccessTokenDataGenerator = hybridFlowAccessTokenDataGenerator;
      this.grantedScopeProcessorsService = grantedScopeProcessorsService;
      this.codeFlowAccessTokenClaimsService = codeFlowAccessTokenClaimsService;
      this.codeFlowIdTokenClaimsService = codeFlowIdTokenClaimsService;
      this.userAgentSessionService = userAgentSessionService;
      this.usePersistedClaims = usePersistedClaims;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2AuthorizationCodeServiceBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private ELogger logger;
      @SuppressFBWarnings(justification = "generated code")
      private AuthorizationServerId authorizationServerId;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2ClientLoader clientLoader;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2GrantsSessionService sessionService;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2AuthorizationCodeGenerator authorizationCodeGenerator;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2OidcCodeGrantRedirectUriGenerator codeGrantUriGenerator;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2OidcAuthorizationCodeGrantSettings settings;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<Optional<OpenIDProviderUserAgentState>> sessionStateService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<CodeFlowOpenIdConnectIdTokenGenerator> codeFlowIdTokenGenerator;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<HybridFlowOpenIdConnectIdTokenDataGenerator> hybridFlowIdTokenDataGenerator;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<OAuth2AccessTokenGenerator> hybridFlowAccessTokenDataGenerator;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<GrantedScopeProcessorsService> grantedScopeProcessorsService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<OAuth2AccessTokenClaimsService> codeFlowAccessTokenClaimsService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<OpenIdConnectCodeFlowIdTokenClaimsService> codeFlowIdTokenClaimsService;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<UserAgentSessionService> userAgentSessionService;
      @SuppressFBWarnings(justification = "generated code")
      private boolean usePersistedClaims;

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

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder logger(@NonNull ELogger logger) {
         if (logger == null) {
            throw new NullPointerException("logger is marked non-null but is null");
         }

         this.logger = logger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder authorizationServerId(@NonNull AuthorizationServerId authorizationServerId) {
         if (authorizationServerId == null) {
            throw new NullPointerException("authorizationServerId is marked non-null but is null");
         }

         this.authorizationServerId = authorizationServerId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder clientLoader(@NonNull OAuth2ClientLoader clientLoader) {
         if (clientLoader == null) {
            throw new NullPointerException("clientLoader is marked non-null but is null");
         }

         this.clientLoader = clientLoader;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder sessionService(@NonNull OAuth2GrantsSessionService sessionService) {
         if (sessionService == null) {
            throw new NullPointerException("sessionService is marked non-null but is null");
         }

         this.sessionService = sessionService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder authorizationCodeGenerator(
         @NonNull OAuth2AuthorizationCodeGenerator authorizationCodeGenerator
      ) {
         if (authorizationCodeGenerator == null) {
            throw new NullPointerException("authorizationCodeGenerator is marked non-null but is null");
         }

         this.authorizationCodeGenerator = authorizationCodeGenerator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder codeGrantUriGenerator(
         @NonNull OAuth2OidcCodeGrantRedirectUriGenerator codeGrantUriGenerator
      ) {
         if (codeGrantUriGenerator == null) {
            throw new NullPointerException("codeGrantUriGenerator is marked non-null but is null");
         }

         this.codeGrantUriGenerator = codeGrantUriGenerator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder settings(@NonNull OAuth2OidcAuthorizationCodeGrantSettings settings) {
         if (settings == null) {
            throw new NullPointerException("settings is marked non-null but is null");
         }

         this.settings = settings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder sessionStateService(
         @NonNull Provider<Optional<OpenIDProviderUserAgentState>> sessionStateService
      ) {
         if (sessionStateService == null) {
            throw new NullPointerException("sessionStateService is marked non-null but is null");
         }

         this.sessionStateService = sessionStateService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder codeFlowIdTokenGenerator(
         @NonNull Provider<CodeFlowOpenIdConnectIdTokenGenerator> codeFlowIdTokenGenerator
      ) {
         if (codeFlowIdTokenGenerator == null) {
            throw new NullPointerException("codeFlowIdTokenGenerator is marked non-null but is null");
         }

         this.codeFlowIdTokenGenerator = codeFlowIdTokenGenerator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder hybridFlowIdTokenDataGenerator(
         @NonNull Provider<HybridFlowOpenIdConnectIdTokenDataGenerator> hybridFlowIdTokenDataGenerator
      ) {
         if (hybridFlowIdTokenDataGenerator == null) {
            throw new NullPointerException("hybridFlowIdTokenDataGenerator is marked non-null but is null");
         }

         this.hybridFlowIdTokenDataGenerator = hybridFlowIdTokenDataGenerator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder hybridFlowAccessTokenDataGenerator(
         @NonNull Provider<OAuth2AccessTokenGenerator> hybridFlowAccessTokenDataGenerator
      ) {
         if (hybridFlowAccessTokenDataGenerator == null) {
            throw new NullPointerException("hybridFlowAccessTokenDataGenerator is marked non-null but is null");
         }

         this.hybridFlowAccessTokenDataGenerator = hybridFlowAccessTokenDataGenerator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder grantedScopeProcessorsService(
         @NonNull Provider<GrantedScopeProcessorsService> grantedScopeProcessorsService
      ) {
         if (grantedScopeProcessorsService == null) {
            throw new NullPointerException("grantedScopeProcessorsService is marked non-null but is null");
         }

         this.grantedScopeProcessorsService = grantedScopeProcessorsService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder codeFlowAccessTokenClaimsService(
         @NonNull Provider<OAuth2AccessTokenClaimsService> codeFlowAccessTokenClaimsService
      ) {
         if (codeFlowAccessTokenClaimsService == null) {
            throw new NullPointerException("codeFlowAccessTokenClaimsService is marked non-null but is null");
         }

         this.codeFlowAccessTokenClaimsService = codeFlowAccessTokenClaimsService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder codeFlowIdTokenClaimsService(
         @NonNull Provider<OpenIdConnectCodeFlowIdTokenClaimsService> codeFlowIdTokenClaimsService
      ) {
         if (codeFlowIdTokenClaimsService == null) {
            throw new NullPointerException("codeFlowIdTokenClaimsService is marked non-null but is null");
         }

         this.codeFlowIdTokenClaimsService = codeFlowIdTokenClaimsService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder userAgentSessionService(
         @NonNull Provider<UserAgentSessionService> userAgentSessionService
      ) {
         if (userAgentSessionService == null) {
            throw new NullPointerException("userAgentSessionService is marked non-null but is null");
         }

         this.userAgentSessionService = userAgentSessionService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder usePersistedClaims(boolean usePersistedClaims) {
         this.usePersistedClaims = usePersistedClaims;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AuthorizationCodeService build() {
         return new OAuth2AuthorizationCodeService(
            this.logger,
            this.authorizationServerId,
            this.clientLoader,
            this.sessionService,
            this.authorizationCodeGenerator,
            this.codeGrantUriGenerator,
            this.settings,
            this.sessionStateService,
            this.codeFlowIdTokenGenerator,
            this.hybridFlowIdTokenDataGenerator,
            this.hybridFlowAccessTokenDataGenerator,
            this.grantedScopeProcessorsService,
            this.codeFlowAccessTokenClaimsService,
            this.codeFlowIdTokenClaimsService,
            this.userAgentSessionService,
            this.usePersistedClaims
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2AuthorizationCodeService.OAuth2AuthorizationCodeServiceBuilder(logger="
            + this.logger
            + ", authorizationServerId="
            + this.authorizationServerId
            + ", clientLoader="
            + this.clientLoader
            + ", sessionService="
            + this.sessionService
            + ", authorizationCodeGenerator="
            + this.authorizationCodeGenerator
            + ", codeGrantUriGenerator="
            + this.codeGrantUriGenerator
            + ", settings="
            + this.settings
            + ", sessionStateService="
            + this.sessionStateService
            + ", codeFlowIdTokenGenerator="
            + this.codeFlowIdTokenGenerator
            + ", hybridFlowIdTokenDataGenerator="
            + this.hybridFlowIdTokenDataGenerator
            + ", hybridFlowAccessTokenDataGenerator="
            + this.hybridFlowAccessTokenDataGenerator
            + ", grantedScopeProcessorsService="
            + this.grantedScopeProcessorsService
            + ", codeFlowAccessTokenClaimsService="
            + this.codeFlowAccessTokenClaimsService
            + ", codeFlowIdTokenClaimsService="
            + this.codeFlowIdTokenClaimsService
            + ", userAgentSessionService="
            + this.userAgentSessionService
            + ", usePersistedClaims="
            + this.usePersistedClaims
            + ")";
      }
   }
}
