package com.airlock.iam.oauth2.infrastructure.discovery;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.domain.model.provider.ProviderId;
import com.airlock.iam.common.domain.service.cache.FailureTolerantExpiringCache;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.oauth2.api.domain.model.clientauthentication.ClientAuthenticationMethod;
import com.airlock.iam.oauth2.domain.model.logging.OAuth2LogTypes;
import com.airlock.iam.oauth2.infrastructure.discovery.compatibility.OpenIDConnectCompatibility;
import com.airlock.iam.oauth2.infrastructure.discovery.compatibility.OpenIDConnectCompatibilityResult;
import com.airlock.iam.oauth2.infrastructure.discovery.compatibility.OpenIDConnectConfigurationTokenEndpointAuthMethods;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import java.time.Duration;
import java.util.Optional;

public final class OpenIDConnectProviderMetadataCache {
   private static final ELogger LOG = ELoggerFactory.getLogger(OpenIDConnectProviderMetadataCache.class);
   private final FailureTolerantExpiringCache<ProviderId, OIDCProviderMetadata> metadataCache;
   private final ProviderId providerId;

   public OpenIDConnectProviderMetadataCache(
      OpenIDConnectProviderMetadataRequestClient client,
      ProviderId providerId,
      Duration cacheRefreshInterval,
      Optional<ClientAuthenticationMethod> configuredClientAuthMethod
   ) {
      this.providerId = providerId;
      this.metadataCache = new FailureTolerantExpiringCache<>(
         cacheRefreshInterval,
         var2x -> loadCompatibleMetadata(client, configuredClientAuthMethod),
         (var1x, failure) -> LOG.warn(
            OAuth2LogTypes.OAUTH2_MESSAGE,
            "Error while reloading provider metadata of "
               + LogUtil.logSafeQuote(providerId)
               + ". Previously fetched metadata will be used: "
               + failure.getMessage()
         )
      );
   }

   public OIDCProviderMetadata cached() {
      return this.metadataCache.get(this.providerId);
   }

   private static OIDCProviderMetadata loadCompatibleMetadata(
      OpenIDConnectProviderMetadataRequestClient client, Optional<ClientAuthenticationMethod> configuredClientAuthMethod
   ) {
      OIDCProviderMetadata metadata;
      try {
         metadata = client.executeRequest();
      } catch (OpenIDConnectProviderMetadataRequestException e) {
         throw new FailureException("Error while requesting provider metadata: " + e.getMessage(), e);
      }

      OpenIDConnectCompatibility validator = OpenIDConnectCompatibility.builder()
         .metadata(metadata)
         .configuration(new OpenIDConnectConfigurationTokenEndpointAuthMethods(configuredClientAuthMethod))
         .build();
      OpenIDConnectCompatibilityResult compatibilityResult = validator.validate();
      Optional<String> incompatibilityMessage = compatibilityResult.getIncompatibilityMessage();
      if (incompatibilityMessage.isPresent()) {
         throw new FailureException("Incompatibilities with the provider detected:\n" + incompatibilityMessage.get());
      }

      compatibilityResult.getWarningMessage()
         .ifPresent(s -> LOG.warn(OAuth2LogTypes.OAUTH2_MESSAGE, "Compatibility check for this provider successful, but it yielded warnings:\n" + s));
      return metadata;
   }
}
