package com.airlock.iam.oauth2.application.service.token.revocation;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.log.audit.AuditLogger;
import com.airlock.iam.oauth2.api.domain.model.OAuth2ClientId;
import com.airlock.iam.oauth2.application.service.revocation.OAuth2TokenRevocationService;
import com.airlock.iam.oauth2.domain.model.logging.OAuth2TokenRepresentation;
import com.airlock.iam.oauth2.domain.model.revocation.TokenInvalidationLogic;
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.token.TokenRevocationTokenManager;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Collection;
import java.util.Optional;
import lombok.NonNull;

public final class OAuth2TokenRevocationServiceImpl implements OAuth2TokenRevocationService {
   @NonNull
   private final TokenRevocationTokenManager tokenManager;
   @NonNull
   private final OAuth2TokenRepresentation tokenRepresentation;
   @NonNull
   private final TokenInvalidationLogic accessTokenInvalidation;
   @NonNull
   private final TokenInvalidationLogic refreshTokenInvalidation;
   @NonNull
   private final ELogger logger;

   @Override
   public void revoke(String tokenValue, OAuth2ClientId clientId) {
      this.logger.debug("Trying to revoke OAuth 2.0 Token: " + this.tokenRepresentation.apply(tokenValue));
      Optional<OAuth2AccessToken> accessToken = this.accessToken(tokenValue, clientId);
      if (accessToken.isPresent()) {
         this.invalidateAccessToken(accessToken.get());
         this.logger.info("Revoked OAuth 2.0 Access Token: " + this.tokenRepresentation.apply(tokenValue));
      } else {
         Optional<OAuth2RefreshToken> refreshToken = this.refreshToken(tokenValue, clientId);
         if (refreshToken.isPresent()) {
            this.invalidateRefreshToken(refreshToken.get());
            this.logger.info("Revoked OAuth 2.0 Refresh Token: " + this.tokenRepresentation.apply(tokenValue));
         }
      }
   }

   private Optional<OAuth2AccessToken> accessToken(String tokenValue, OAuth2ClientId clientId) {
      return this.tokenManager.getAccessTokenWithoutInvalidation(tokenValue, clientId);
   }

   private void invalidateAccessToken(OAuth2AccessToken accessToken) {
      Collection<OAuth2Token> tokensToInvalidate = this.accessTokenInvalidation.tokens(accessToken);
      this.invalidate(tokensToInvalidate, accessToken.getUserId().getValue());
   }

   private Optional<OAuth2RefreshToken> refreshToken(String tokenValue, OAuth2ClientId clientId) {
      return this.tokenManager.getRefreshToken(tokenValue, clientId);
   }

   private void invalidateRefreshToken(OAuth2RefreshToken refreshToken) {
      Collection<OAuth2Token> tokensToInvalidate = this.refreshTokenInvalidation.tokens(refreshToken);
      this.invalidate(tokensToInvalidate, refreshToken.getUserId().getValue());
   }

   private void invalidate(Collection<OAuth2Token> tokensToInvalidate, String username) {
      for (OAuth2Token token : tokensToInvalidate) {
         this.tokenManager.invalidate(token);
      }

      if (!tokensToInvalidate.isEmpty()) {
         auditLog(username, tokensToInvalidate.size());
      }
   }

   private static void auditLog(String username, int numberOfTokens) {
      AuditLogger.log(
         OAuth2TokenRevocationServiceImpl.class, String.format("Revoked %d OAuth 2.0 Token(s) for user %s", numberOfTokens, LogUtil.quote(username))
      );
   }

   @SuppressFBWarnings(justification = "generated code")
   OAuth2TokenRevocationServiceImpl(
      @NonNull TokenRevocationTokenManager tokenManager,
      @NonNull OAuth2TokenRepresentation tokenRepresentation,
      @NonNull TokenInvalidationLogic accessTokenInvalidation,
      @NonNull TokenInvalidationLogic refreshTokenInvalidation,
      @NonNull ELogger logger
   ) {
      if (tokenManager == null) {
         throw new NullPointerException("tokenManager is marked non-null but is null");
      }

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

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

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

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

      this.tokenManager = tokenManager;
      this.tokenRepresentation = tokenRepresentation;
      this.accessTokenInvalidation = accessTokenInvalidation;
      this.refreshTokenInvalidation = refreshTokenInvalidation;
      this.logger = logger;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2TokenRevocationServiceImplBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private TokenRevocationTokenManager tokenManager;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2TokenRepresentation tokenRepresentation;
      @SuppressFBWarnings(justification = "generated code")
      private TokenInvalidationLogic accessTokenInvalidation;
      @SuppressFBWarnings(justification = "generated code")
      private TokenInvalidationLogic refreshTokenInvalidation;
      @SuppressFBWarnings(justification = "generated code")
      private ELogger logger;

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

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2TokenRevocationServiceImpl.OAuth2TokenRevocationServiceImplBuilder tokenManager(@NonNull TokenRevocationTokenManager tokenManager) {
         if (tokenManager == null) {
            throw new NullPointerException("tokenManager is marked non-null but is null");
         }

         this.tokenManager = tokenManager;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2TokenRevocationServiceImpl.OAuth2TokenRevocationServiceImplBuilder tokenRepresentation(
         @NonNull OAuth2TokenRepresentation tokenRepresentation
      ) {
         if (tokenRepresentation == null) {
            throw new NullPointerException("tokenRepresentation is marked non-null but is null");
         }

         this.tokenRepresentation = tokenRepresentation;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2TokenRevocationServiceImpl.OAuth2TokenRevocationServiceImplBuilder accessTokenInvalidation(
         @NonNull TokenInvalidationLogic accessTokenInvalidation
      ) {
         if (accessTokenInvalidation == null) {
            throw new NullPointerException("accessTokenInvalidation is marked non-null but is null");
         }

         this.accessTokenInvalidation = accessTokenInvalidation;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2TokenRevocationServiceImpl.OAuth2TokenRevocationServiceImplBuilder refreshTokenInvalidation(
         @NonNull TokenInvalidationLogic refreshTokenInvalidation
      ) {
         if (refreshTokenInvalidation == null) {
            throw new NullPointerException("refreshTokenInvalidation is marked non-null but is null");
         }

         this.refreshTokenInvalidation = refreshTokenInvalidation;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2TokenRevocationServiceImpl.OAuth2TokenRevocationServiceImplBuilder 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 OAuth2TokenRevocationServiceImpl build() {
         return new OAuth2TokenRevocationServiceImpl(
            this.tokenManager, this.tokenRepresentation, this.accessTokenInvalidation, this.refreshTokenInvalidation, this.logger
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2TokenRevocationServiceImpl.OAuth2TokenRevocationServiceImplBuilder(tokenManager="
            + this.tokenManager
            + ", tokenRepresentation="
            + this.tokenRepresentation
            + ", accessTokenInvalidation="
            + this.accessTokenInvalidation
            + ", refreshTokenInvalidation="
            + this.refreshTokenInvalidation
            + ", logger="
            + this.logger
            + ")";
      }
   }
}
