package com.airlock.iam.oauth2.domain.model.token.introspection;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.persistency.exception.PersisterException;
import com.airlock.iam.common.api.domain.model.user.PersistentUser;
import com.airlock.iam.elog.api.ELogger;
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.logging.OAuth2TokenRepresentation;
import com.airlock.iam.oauth2.domain.model.token.OAuth2Token;
import com.airlock.iam.oauth2.domain.service.persistence.OAuth2UserLoader;
import com.airlock.iam.oauth2.domain.service.token.OAuth2LoginTokenService;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import lombok.NonNull;

public final class OAuth2TokenIntrospectionTokenManagerImpl implements OAuth2TokenIntrospectionTokenManager {
   @NonNull
   private final OAuth2LoginTokenService loginTokenService;
   @NonNull
   private final OAuth2UserLoader userLoader;
   @NonNull
   private final OAuth2ClientLoader clientLoader;
   @NonNull
   private final ELogger logger;
   @NonNull
   private final OAuth2TokenRepresentation tokenRepresentation;

   @Override
   public Optional<OAuth2Token> getAccessOrRefreshTokenByTokenValue(String rawTokenValue) {
      Optional<OAuth2Token> token = this.findToken(rawTokenValue);
      if (token.isEmpty()) {
         return Optional.empty();
      } else {
         return !this.isClientValid(token.get()) ? Optional.empty() : token;
      }
   }

   private Optional<OAuth2Token> findToken(String rawTokenValue) {
      Optional<OAuth2Token> accessToken = this.getAccessToken(this.loginTokenService, rawTokenValue);
      if (accessToken.isPresent()) {
         return accessToken;
      }

      Optional<OAuth2Token> refreshToken = this.getRefreshToken(this.loginTokenService, rawTokenValue);
      return refreshToken.isPresent() ? refreshToken : Optional.empty();
   }

   private boolean isClientValid(OAuth2Token token) {
      Optional<OAuth2Client> client = this.clientLoader.tryLoadClient(token.getClientId());
      if (client.isEmpty()) {
         this.logger
            .debug(
               String.format(
                  "Client %s not found on authorization server %s",
                  LogUtil.quote(token.getClientId().id()),
                  LogUtil.quote(token.getAuthorizationServerId().id())
               )
            );
         return false;
      } else if (client.get().lockInfo().isPresent()) {
         this.logger.info(String.format("Client with ID %s is locked", LogUtil.quote(client.get().clientId())));
         return false;
      } else {
         return true;
      }
   }

   private Optional<OAuth2Token> getAccessToken(OAuth2LoginTokenService tokenService, String rawTokenValue) {
      try {
         return tokenService.getAccessTokenWithoutInvalidation(rawTokenValue, Optional.empty()).map(OAuth2Token.class::cast);
      } catch (PersisterException e) {
         this.logger
            .info("Error loading access token " + LogUtil.quote(this.tokenRepresentation.apply(rawTokenValue)) + " from persistency: " + e.getMessage(), e);
         return Optional.empty();
      }
   }

   private Optional<OAuth2Token> getRefreshToken(OAuth2LoginTokenService tokenService, String rawTokenValue) {
      try {
         return tokenService.getRefreshTokenWithoutInvalidationAndWithoutClientIdValidation(rawTokenValue).map(OAuth2Token.class::cast);
      } catch (PersisterException e) {
         this.logger
            .info("Error loading refresh token " + LogUtil.quote(this.tokenRepresentation.apply(rawTokenValue)) + " from persistency: " + e.getMessage(), e);
         return Optional.empty();
      }
   }

   @Override
   public Optional<PersistentUser> validUser(OAuth2Token oAuth2Token) {
      return this.userLoader.getValidUser(oAuth2Token);
   }

   @SuppressFBWarnings(justification = "generated code")
   public OAuth2TokenIntrospectionTokenManagerImpl(
      @NonNull OAuth2LoginTokenService loginTokenService,
      @NonNull OAuth2UserLoader userLoader,
      @NonNull OAuth2ClientLoader clientLoader,
      @NonNull ELogger logger,
      @NonNull OAuth2TokenRepresentation tokenRepresentation
   ) {
      if (loginTokenService == null) {
         throw new NullPointerException("loginTokenService is marked non-null but is null");
      }

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

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

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

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

      this.loginTokenService = loginTokenService;
      this.userLoader = userLoader;
      this.clientLoader = clientLoader;
      this.logger = logger;
      this.tokenRepresentation = tokenRepresentation;
   }
}
