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

import com.airlock.iam.base.api.domain.model.store.exception.StoreFailureException;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.base.misc.result.Result;
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.elog.api.ELoggerFactory;
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.credential.OAuth2AccessInfo;
import com.airlock.iam.oauth2.domain.model.credential.OAuth2TokenBindingCredential;
import com.airlock.iam.oauth2.domain.model.credential.OAuth2TokenBindingCredentialExtractor;
import com.airlock.iam.oauth2.domain.model.token.OAuth2AccessToken;
import com.airlock.iam.oauth2.domain.model.token.RawOAuth2Token;
import com.airlock.iam.oauth2.domain.model.validation.OAuth2ValidationResult;
import com.airlock.iam.oauth2.domain.model.validation.OAuth2ValidationStatus;
import com.airlock.iam.oauth2.domain.service.persistence.OAuth2UserLoader;
import com.airlock.iam.oauth2.domain.service.token.OAuth2LoginTokenService;
import com.airlock.iam.oauth2.domain.service.token.OAuth2TokenBindingVerifier;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;

public class OAuth2AccessTokenValidationServiceImpl implements OAuth2AccessTokenValidationService {
   private static final ELogger LOG = ELoggerFactory.getLogger(OAuth2AccessTokenValidationServiceImpl.class);
   private final Provider<OAuth2LoginTokenService> tokenService;
   private final Provider<OAuth2UserLoader> userLoader;
   private final Provider<OAuth2ClientLoader> clientLoader;
   private final Provider<OAuth2TokenBindingCredentialExtractor> bindingCredentialExtractor;
   private final Provider<OAuth2TokenBindingVerifier> bindingVerifier;

   @Inject
   public OAuth2AccessTokenValidationServiceImpl(
      Provider<OAuth2LoginTokenService> tokenService,
      Provider<OAuth2UserLoader> userLoader,
      Provider<OAuth2ClientLoader> clientLoader,
      Provider<OAuth2TokenBindingCredentialExtractor> bindingCredentialExtractor,
      Provider<OAuth2TokenBindingVerifier> bindingVerifier
   ) {
      this.tokenService = tokenService;
      this.userLoader = userLoader;
      this.clientLoader = clientLoader;
      this.bindingCredentialExtractor = bindingCredentialExtractor;
      this.bindingVerifier = bindingVerifier;
   }

   @Override
   public Result<OAuth2AccessInfo, OAuth2ValidationResult> validateAccessToken(RawOAuth2Token rawToken) {
      Optional<OAuth2TokenBindingCredential> bindingCredential = ((OAuth2TokenBindingCredentialExtractor)this.bindingCredentialExtractor.get()).extract();
      Optional<OAuth2AccessToken> accessToken = this.loadAndValidateAccessToken(rawToken, bindingCredential);
      if (accessToken.isEmpty()) {
         LOG.info("Request contains an invalid access token.");
         return Result.failure(new OAuth2ValidationResult(OAuth2ValidationStatus.BEARER_TOKEN_NOT_VALID));
      } else {
         Optional<OAuth2Client> oAuth2Client = ((OAuth2ClientLoader)this.clientLoader.get()).tryLoadClient(accessToken.get().getClientId());
         if (oAuth2Client.isEmpty()) {
            LOG.info("Client of access token not found in persistency or configuration: " + LogUtil.logSafeQuote(accessToken.get().getClientId().id()));
            return Result.failure(new OAuth2ValidationResult(OAuth2ValidationStatus.CLIENT_NOT_FOUND));
         } else if (oAuth2Client.get().lockInfo().isPresent()) {
            LOG.info(String.format("Client with ID %s is locked", LogUtil.quote(oAuth2Client.get().clientId())));
            return Result.failure(new OAuth2ValidationResult(OAuth2ValidationStatus.CLIENT_LOCKED));
         } else {
            Optional<PersistentUser> validUser = ((OAuth2UserLoader)this.userLoader.get()).getValidUser(accessToken.get());
            return validUser.isEmpty()
               ? Result.failure(new OAuth2ValidationResult(OAuth2ValidationStatus.USER_NOT_VALID))
               : Result.result(new OAuth2AccessInfo(rawToken, accessToken.get(), validUser.get()));
         }
      }
   }

   private Optional<OAuth2AccessToken> loadAndValidateAccessToken(RawOAuth2Token rawToken, Optional<OAuth2TokenBindingCredential> credential) {
      return this.loadAccessToken(rawToken)
         .filter(
            token -> token.getTokenBinding().map(binding -> ((OAuth2TokenBindingVerifier)this.bindingVerifier.get()).verify(binding, credential)).orElse(true)
         );
   }

   private Optional<OAuth2AccessToken> loadAccessToken(RawOAuth2Token rawToken) {
      try {
         return ((OAuth2LoginTokenService)this.tokenService.get()).getAccessTokenWithoutClientIdValidation(rawToken.toTokenString());
      } catch (PersisterException e) {
         throw new StoreFailureException("Error loading or updating access token: " + e.getMessage(), e);
      }
   }
}
