package com.airlock.iam.oauth2.infrastructure.service.tokenexchange;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.base.misc.result.Result;
import com.airlock.iam.base.misc.time.TimeProvider;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.oauth2.api.domain.model.tokenexchange.OAuth2TokenExchangeRawToken;
import com.airlock.iam.oauth2.api.domain.model.tokenexchange.OAuth2TokenExchangeTokenType;
import com.airlock.iam.oauth2.api.domain.model.tokenexchange.jwt.OAuth2TokenExchangeValidatedTokenData;
import com.airlock.iam.oauth2.domain.model.logger.IntegrationLoggerFactory;
import com.airlock.iam.oauth2.domain.model.logging.OAuth2LogTypes;
import com.airlock.iam.oauth2.domain.model.logging.OAuth2TokenRepresentation;
import com.airlock.iam.oauth2.domain.model.tokenexchange.OAuth2TokenExchangeTokenIssuerId;
import com.airlock.iam.oauth2.domain.model.tokenexchange.rules.jwt.SignedOrUnsignedJwtClaims;
import com.airlock.iam.oauth2.domain.model.validation.OAuth2Error;
import com.airlock.iam.oauth2.domain.service.tokenexchange.validation.OAuth2TokenExchangeTokenValidationService;
import com.airlock.iam.oauth2.domain.service.tokenexchange.validation.OAuth2TokenExchangeValidationServiceSettings;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;

public final class OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImpl implements OAuth2TokenExchangeTokenValidationService {
   private final Provider<IntegrationLoggerFactory> loggerFactory;
   private final Provider<OAuth2TokenExchangeValidationServiceSettings> settings;
   private final Provider<OAuth2TokenRepresentation> tokenRepresentation;
   private final Provider<TimeProvider> timeProvider;
   private ELogger log;

   @Inject
   public OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImpl(
      Provider<OAuth2TokenExchangeValidationServiceSettings> settings,
      Provider<IntegrationLoggerFactory> loggerFactory,
      Provider<OAuth2TokenRepresentation> tokenRepresentation,
      Provider<TimeProvider> timeProvider
   ) {
      this.settings = settings;
      this.loggerFactory = loggerFactory;
      this.tokenRepresentation = tokenRepresentation;
      this.timeProvider = timeProvider;
   }

   @Override
   public Result<OAuth2TokenExchangeValidatedTokenData, OAuth2Error> validate(OAuth2TokenExchangeRawToken token) {
      OAuth2TokenExchangeTokenType tokenType = token.getType();
      if (!this.isAllowedTokenType(tokenType)) {
         this.getLogger()
            .info(
               OAuth2LogTypes.OAUTH2_MESSAGE,
               String.format("%s token type %s not supported.", StringUtils.capitalize(this.tokenKind()), LogUtil.logSafeQuote(tokenType.toURI()))
            );
         return Result.failure(OAuth2Error.INVALID_REQUEST);
      } else {
         SignedOrUnsignedJwtClaims parsedToken = SignedOrUnsignedJwtClaims.signedOrUnsignedJwtClaims(token.getValue());
         if (parsedToken.isEmpty()) {
            this.getLogger()
               .info(
                  OAuth2LogTypes.OAUTH2_MESSAGE,
                  String.format(
                     "Could not parse %s token as signed JWT: %s",
                     this.tokenKind(),
                     ((OAuth2TokenRepresentation)this.tokenRepresentation.get()).apply(token.getValue())
                  )
               );
            return Result.failure(OAuth2Error.INVALID_REQUEST);
         } else if (!parsedToken.isValidAt(((TimeProvider)this.timeProvider.get()).getCurrentInstant())) {
            this.getLogger()
               .info(OAuth2LogTypes.OAUTH2_MESSAGE, "%s token is either expired or not yet valid.".formatted(StringUtils.capitalize(this.tokenKind())));
            return Result.failure(OAuth2Error.INVALID_REQUEST);
         } else if (parsedToken.getSubject().isEmpty()) {
            this.getLogger().info(OAuth2LogTypes.OAUTH2_MESSAGE, "%s token does not contain a subject.".formatted(StringUtils.capitalize(this.tokenKind())));
            return Result.failure(OAuth2Error.INVALID_REQUEST);
         } else {
            Optional<String> issuerId = parsedToken.getIssuer();
            if (issuerId.isEmpty()) {
               this.getLogger().info(OAuth2LogTypes.OAUTH2_MESSAGE, "Issuer is missing from %s token.".formatted(this.tokenKind()));
               return Result.failure(OAuth2Error.INVALID_REQUEST);
            } else if (!this.isAllowedIssuer(issuerId.get())) {
               this.getLogger()
                  .info(
                     OAuth2LogTypes.OAUTH2_MESSAGE,
                     String.format(
                        "%s token issuer %s is not configured as an allowed issuer.",
                        StringUtils.capitalize(this.tokenKind()),
                        LogUtil.logSafeQuote(issuerId.get())
                     )
                  );
               return Result.failure(OAuth2Error.INVALID_REQUEST);
            } else {
               return Result.result(new OAuth2TokenExchangeValidatedTokenData(parsedToken.getClaimSet().orElseThrow().claims()));
            }
         }
      }
   }

   private boolean isAllowedTokenType(OAuth2TokenExchangeTokenType tokenUriType) {
      return ((OAuth2TokenExchangeValidationServiceSettings)this.settings.get()).getAllowedTokenTypes().contains(tokenUriType);
   }

   private String tokenKind() {
      return ((OAuth2TokenExchangeValidationServiceSettings)this.settings.get()).getTokenKind().getDescription();
   }

   private ELogger getLogger() {
      if (this.log == null) {
         this.log = ((IntegrationLoggerFactory)this.loggerFactory.get()).create(OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImpl.class);
      }

      return this.log;
   }

   private boolean isAllowedIssuer(String issuer) {
      return ((OAuth2TokenExchangeValidationServiceSettings)this.settings.get()).getAllowedIssuers().isEmpty()
         || ((OAuth2TokenExchangeValidationServiceSettings)this.settings.get())
            .getAllowedIssuers()
            .stream()
            .map(OAuth2TokenExchangeTokenIssuerId::getIssuerId)
            .anyMatch(issuer::equals);
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImplBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Provider<OAuth2TokenExchangeValidationServiceSettings> settings;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<IntegrationLoggerFactory> loggerFactory;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<OAuth2TokenRepresentation> tokenRepresentation;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<TimeProvider> timeProvider;

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

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImpl.OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImplBuilder settings(
         Provider<OAuth2TokenExchangeValidationServiceSettings> settings
      ) {
         this.settings = settings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImpl.OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImplBuilder loggerFactory(
         Provider<IntegrationLoggerFactory> loggerFactory
      ) {
         this.loggerFactory = loggerFactory;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImpl.OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImplBuilder tokenRepresentation(
         Provider<OAuth2TokenRepresentation> tokenRepresentation
      ) {
         this.tokenRepresentation = tokenRepresentation;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImpl.OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImplBuilder timeProvider(
         Provider<TimeProvider> timeProvider
      ) {
         this.timeProvider = timeProvider;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImpl build() {
         return new OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImpl(
            this.settings, this.loggerFactory, this.tokenRepresentation, this.timeProvider
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImpl.OAuth2TokenExchangeUncheckedSignatureTokenValidationServiceImplBuilder(settings="
            + this.settings
            + ", loggerFactory="
            + this.loggerFactory
            + ", tokenRepresentation="
            + this.tokenRepresentation
            + ", timeProvider="
            + this.timeProvider
            + ")";
      }
   }
}
