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

import com.airlock.iam.common.api.domain.model.persistency.exception.PersisterException;
import com.airlock.iam.oauth2.api.domain.model.OAuth2Scope;
import com.airlock.iam.oauth2.api.domain.model.clientauthentication.OAuth2ClientAuthentication;
import com.airlock.iam.oauth2.domain.model.client.OAuth2Client;
import com.airlock.iam.oauth2.domain.model.session.OAuth2SessionId;
import com.airlock.iam.oauth2.domain.model.token.OAuth2RawAccessTokenContainer;
import com.airlock.iam.oauth2.domain.model.token.OpenIDConnectIDToken;
import com.airlock.iam.oauth2.domain.model.token.OpenIDConnectRawAccessTokenContainer;
import com.airlock.iam.oauth2.domain.service.GrantedScopeProcessorsService;
import com.airlock.iam.oauth2.domain.service.persistence.OAuth2TokenReader;
import com.airlock.iam.oauth2.domain.service.persistence.OAuth2TokenUpdater;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import java.util.Set;
import lombok.NonNull;

public final class DefaultOpenIDConnectCodeGrantTokenManager implements OpenIDConnectCodeGrantTokenManager {
   @NonNull
   private final OAuth2CodeGrantTokenManager oAuth2CodeGrantTokenManager;
   @NonNull
   private final OAuth2TokenReader tokenReader;
   @NonNull
   private final OAuth2TokenUpdater tokenUpdater;
   @NonNull
   private final GrantedScopeProcessorsService grantedScopeProcessorsService;

   @Override
   public Optional<OpenIDConnectRawAccessTokenContainer> exchangeAuthorizationCodeForAccessTokenAndIdToken(
      String authorizationCode,
      OAuth2Client client,
      OAuth2ClientAuthentication clientAuthentication,
      Optional<String> pkceCodeVerifier,
      Optional<String> redirectUriFromRequest
   ) throws PersisterException {
      Optional<OAuth2RawAccessTokenContainer> tokenContainer = this.oAuth2CodeGrantTokenManager
         .exchangeAuthorizationCodeForAccessToken(authorizationCode, client, clientAuthentication, pkceCodeVerifier, redirectUriFromRequest);
      if (tokenContainer.isEmpty()) {
         return Optional.empty();
      }

      OAuth2SessionId sessionId = tokenContainer.get().getAccessToken().getSessionId();
      return Optional.of(new OpenIDConnectRawAccessTokenContainer(tokenContainer.get(), this.getIdToken(sessionId.getId(), client, clientAuthentication)));
   }

   private OpenIDConnectIDToken getIdToken(String sessionId, OAuth2Client client, OAuth2ClientAuthentication clientAuthentication) throws PersisterException {
      Optional<OpenIDConnectIDToken> idToken = this.tokenReader.getIDTokenWithSession(sessionId);
      if (idToken.isEmpty()) {
         throw new PersisterException("Unable to get OpenID Connect id token: No token received.");
      } else {
         return this.processScopes(idToken.get(), client, clientAuthentication);
      }
   }

   private OpenIDConnectIDToken processScopes(OpenIDConnectIDToken idToken, OAuth2Client client, OAuth2ClientAuthentication clientAuthentication) {
      Set<OAuth2Scope> processedScopes = this.grantedScopeProcessorsService.processScopes(idToken.getScopes().scopesAsSet(), client, clientAuthentication);
      if (!idToken.getScopes().scopesAsSet().equals(processedScopes)) {
         this.tokenUpdater.updateTokenScopes(idToken, processedScopes);
      }

      return idToken;
   }

   @SuppressFBWarnings(justification = "generated code")
   public DefaultOpenIDConnectCodeGrantTokenManager(
      @NonNull OAuth2CodeGrantTokenManager oAuth2CodeGrantTokenManager,
      @NonNull OAuth2TokenReader tokenReader,
      @NonNull OAuth2TokenUpdater tokenUpdater,
      @NonNull GrantedScopeProcessorsService grantedScopeProcessorsService
   ) {
      if (oAuth2CodeGrantTokenManager == null) {
         throw new NullPointerException("oAuth2CodeGrantTokenManager is marked non-null but is null");
      }

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

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

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

      this.oAuth2CodeGrantTokenManager = oAuth2CodeGrantTokenManager;
      this.tokenReader = tokenReader;
      this.tokenUpdater = tokenUpdater;
      this.grantedScopeProcessorsService = grantedScopeProcessorsService;
   }
}
