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

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.OAuth2ClientId;
import com.airlock.iam.oauth2.domain.model.token.OAuth2AccessToken;
import com.airlock.iam.oauth2.domain.model.token.OAuth2RefreshToken;
import com.airlock.iam.oauth2.domain.model.token.OAuth2Token;
import com.airlock.iam.oauth2.domain.service.persistence.OAuth2TokenDeleter;
import com.airlock.iam.oauth2.domain.service.persistence.OAuth2TokenReader;
import com.airlock.iam.oauth2.domain.service.persistence.OAuth2TokenUpdater;
import com.airlock.iam.oauth2.domain.service.validation.OAuth2TokenValidation;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import lombok.NonNull;

public final class OAuth2LoginTokenServiceImpl implements OAuth2LoginTokenService {
   @NonNull
   private final OAuth2TokenReader tokenReader;
   @NonNull
   private final OAuth2TokenUpdater tokenUpdater;
   @NonNull
   private final OAuth2TokenDeleter tokenDeleter;
   @NonNull
   private final OAuth2TokenValidation tokenValidation;
   private final boolean singleUseAccessTokens;
   @NonNull
   private final ELogger logger;

   @Override
   public Optional<OAuth2AccessToken> getAccessTokenWithoutClientIdValidation(String tokenString) throws PersisterException {
      Optional<OAuth2AccessToken> oauth2Token = this.getActiveAccessTokenWithoutInvalidation(tokenString, Optional.empty());
      this.invalidateSingleUseAccessToken(oauth2Token);
      return oauth2Token;
   }

   @Override
   public Optional<OAuth2AccessToken> getAccessTokenWithoutInvalidation(String tokenString, Optional<OAuth2ClientId> clientId) throws PersisterException {
      return this.getActiveAccessTokenWithoutInvalidation(tokenString, clientId);
   }

   @Override
   public Optional<OAuth2RefreshToken> getRefreshTokenWithoutInvalidation(String tokenString, OAuth2ClientId clientId) throws PersisterException {
      return this.getActiveRefreshToken(tokenString, Optional.of(clientId));
   }

   @Override
   public Optional<OAuth2RefreshToken> getRefreshTokenWithoutInvalidationAndWithoutClientIdValidation(String tokenString) throws PersisterException {
      return this.getActiveRefreshToken(tokenString, Optional.empty());
   }

   private Optional<OAuth2RefreshToken> getActiveRefreshToken(String tokenString, Optional<OAuth2ClientId> clientId) throws PersisterException {
      Optional<OAuth2Token> oauth2Token = this.tokenReader
         .getActiveTokenFromProviderWithFullTokenString(tokenString, OAuth2Token.OAuth2TokenType.REFRESH_TOKEN);
      oauth2Token.ifPresent(this.tokenUpdater::increaseTokenUsage);
      return !oauth2Token.isEmpty() && this.tokenValidation.isValidTokenCheckAssignment(oauth2Token.get(), clientId)
         ? oauth2Token.map(OAuth2RefreshToken.class::cast)
         : Optional.empty();
   }

   private Optional<OAuth2AccessToken> getActiveAccessTokenWithoutInvalidation(String tokenString, Optional<OAuth2ClientId> clientId) throws PersisterException {
      Optional<OAuth2Token> oauth2Token = this.tokenReader.getActiveTokenFromProviderWithFullTokenString(tokenString, OAuth2Token.OAuth2TokenType.ACCESS_TOKEN);
      oauth2Token.ifPresent(this.tokenUpdater::increaseTokenUsage);
      return !oauth2Token.isEmpty() && this.tokenValidation.isValidTokenCheckAssignment(oauth2Token.get(), clientId)
         ? oauth2Token.map(OAuth2AccessToken.class::cast)
         : Optional.empty();
   }

   private void invalidateSingleUseAccessToken(Optional<OAuth2AccessToken> oauth2Token) throws PersisterException {
      if (oauth2Token.isPresent() && this.singleUseAccessTokens) {
         this.logger.debug("Invalidating old access token after using it");
         this.tokenDeleter.removeToken(oauth2Token.get());
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   public OAuth2LoginTokenServiceImpl(
      @NonNull OAuth2TokenReader tokenReader,
      @NonNull OAuth2TokenUpdater tokenUpdater,
      @NonNull OAuth2TokenDeleter tokenDeleter,
      @NonNull OAuth2TokenValidation tokenValidation,
      boolean singleUseAccessTokens,
      @NonNull ELogger logger
   ) {
      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 (tokenDeleter == null) {
         throw new NullPointerException("tokenDeleter is marked non-null but is null");
      }

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

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

      this.tokenReader = tokenReader;
      this.tokenUpdater = tokenUpdater;
      this.tokenDeleter = tokenDeleter;
      this.tokenValidation = tokenValidation;
      this.singleUseAccessTokens = singleUseAccessTokens;
      this.logger = logger;
   }
}
