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

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.domain.service.cache.FailureTolerantExpiringCache;
import com.airlock.iam.core.domain.model.http.HttpRequest;
import com.airlock.iam.core.domain.model.http.HttpRequestMethod;
import com.airlock.iam.core.domain.model.http.HttpRequestUri;
import com.airlock.iam.core.domain.model.http.HttpResponse;
import com.airlock.iam.core.domain.model.http.client.HttpClient;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.oauth2.domain.model.logging.OAuth2LogTypes;
import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import java.net.URI;
import java.time.Duration;

public final class HttpClientRemoteDiscoveryLoader implements RemoteDiscoveryLoader {
   private static final ELogger LOG = ELoggerFactory.getLogger(HttpClientRemoteDiscoveryLoader.class);
   private final FailureTolerantExpiringCache<URI, OIDCProviderMetadata> discoveryCache;

   public HttpClientRemoteDiscoveryLoader(HttpClient httpClient, Duration cacheRefreshInterval) {
      this.discoveryCache = new FailureTolerantExpiringCache<>(
         cacheRefreshInterval,
         url -> loadDiscoveryMetadata(httpClient, url),
         (var0, failure) -> LOG.warn(
            OAuth2LogTypes.OAUTH2_MESSAGE,
            "Could not refresh OpenID Connect discovery document; keeping the previous data in the cache: " + failure.getMessage()
         )
      );
   }

   @Override
   public URI getJwksUriFromDiscoveryEndpoint(URI location) {
      URI jwkSetURI = this.discoveryCache.get(location).getJWKSetURI();
      if (jwkSetURI == null) {
         throw new FailureException("Discovery response did not contain a JWKS URI.");
      } else {
         return jwkSetURI;
      }
   }

   private static OIDCProviderMetadata loadDiscoveryMetadata(HttpClient httpClient, URI location) {
      HttpResponse discoveryResponse;
      try {
         LOG.info("Loading OpenId Connect Discovery document from URI " + LogUtil.logSafeQuote(location.toASCIIString()));
         discoveryResponse = httpClient.execute(HttpRequest.builder().uri(HttpRequestUri.httpRequestUri(location)).method(HttpRequestMethod.GET).build());
      } catch (FailureException e) {
         throw new FailureException("Error fetching from URI " + LogUtil.logSafeQuote(location.toASCIIString()) + ": " + e.getMessage(), e);
      }

      if (!discoveryResponse.hasSuccessStatus()) {
         throw new FailureException("Error fetching from URI " + LogUtil.logSafeQuote(location.toASCIIString()) + ": got status " + discoveryResponse.status());
      }

      try {
         return OIDCProviderMetadata.parse(discoveryResponse.body());
      } catch (ParseException e) {
         throw new FailureException("Failed to parse response from " + LogUtil.logSafeQuote(location.toASCIIString()) + ": " + e.getMessage());
      }
   }
}
