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

import com.airlock.iam.base.domain.service.uri.IamUriBuilder;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.oauth2.api.domain.model.AuthorizationServerId;
import com.airlock.iam.oauth2.api.domain.model.OAuth2ClientId;
import com.airlock.iam.oauth2.domain.model.logging.OAuth2TokenRepresentation;
import com.airlock.iam.oauth2.domain.model.logoutaction.BackchannelLogout;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.message.BasicNameValuePair;

public class BackchannelLogoutImpl implements BackchannelLogout {
   private static final ELogger LOG = ELoggerFactory.getLogger(BackchannelLogoutImpl.class);
   private final AuthorizationServerId openIdProvider;
   private final OAuth2ClientId relyingParty;
   private final URI backchannelLogoutUri;
   private final String logoutToken;
   private final Supplier<CloseableHttpClient> httpClientSupplier;
   private final OAuth2TokenRepresentation tokenRepresentation;

   public String call() throws IOException {
      try {
         CloseableHttpClient httpClient = this.httpClientSupplier.get();

         String var2;
         try {
            LOG.debug("Execute OpenID Connect Back-Channel Logout using token: " + LogUtil.quote(this.tokenRepresentation.apply(this.logoutToken)));
            var2 = (String)httpClient.execute(this.createBackchannelLogoutRequest(this.logoutToken), this::checkStatusCode);
         } catch (Throwable var5) {
            if (httpClient != null) {
               try {
                  httpClient.close();
               } catch (Throwable var4) {
                  var5.addSuppressed(var4);
               }
            }

            throw var5;
         }

         if (httpClient != null) {
            httpClient.close();
         }

         return var2;
      } catch (IOException e) {
         throw new IOException(
            String.format(
               "OP %s failed to perform OIDC back-channel logout for RP %s at %s: %s",
               LogUtil.logSafeQuote(this.openIdProvider),
               LogUtil.logSafeQuote(this.relyingParty),
               LogUtil.logSafeQuote(this.backchannelLogoutUri),
               e.getMessage()
            ),
            e
         );
      }
   }

   private String checkStatusCode(ClassicHttpResponse response) throws IOException, ParseException {
      int statusCode = response.getCode();
      if (isOk(statusCode)) {
         return String.format(
            "OP %s successfully performed back-channel logout for RP %s at %s.",
            LogUtil.logSafeQuote(this.openIdProvider),
            LogUtil.logSafeQuote(this.relyingParty),
            LogUtil.logSafeQuote(this.backchannelLogoutUri)
         );
      } else if (statusCode == 400) {
         throw new IOException("RP returned status 400 with body " + LogUtil.logSafeQuote(StringUtils.abbreviate(responseBody(response), 200)));
      } else {
         throw new IOException(String.format("RP returned status %d", statusCode));
      }
   }

   private static String responseBody(ClassicHttpResponse response) throws IOException, ParseException {
      return response.getEntity() == null ? "" : EntityUtils.toString(response.getEntity());
   }

   private static boolean isOk(Integer statusCode) {
      return statusCode == 200 || statusCode == 204;
   }

   private HttpPost createBackchannelLogoutRequest(String logoutToken) {
      HttpPost request = new HttpPost(IamUriBuilder.fromUri(this.backchannelLogoutUri).build());
      request.setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType());
      List<NameValuePair> urlParameters = new ArrayList<>();
      urlParameters.add(new BasicNameValuePair("logout_token", logoutToken));
      request.setEntity(new UrlEncodedFormEntity(urlParameters));
      return request;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   private BackchannelLogoutImpl(
      AuthorizationServerId openIdProvider,
      OAuth2ClientId relyingParty,
      URI backchannelLogoutUri,
      String logoutToken,
      Supplier<CloseableHttpClient> httpClientSupplier,
      OAuth2TokenRepresentation tokenRepresentation
   ) {
      this.openIdProvider = openIdProvider;
      this.relyingParty = relyingParty;
      this.backchannelLogoutUri = backchannelLogoutUri;
      this.logoutToken = logoutToken;
      this.httpClientSupplier = httpClientSupplier;
      this.tokenRepresentation = tokenRepresentation;
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public AuthorizationServerId getOpenIdProvider() {
      return this.openIdProvider;
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public OAuth2ClientId getRelyingParty() {
      return this.relyingParty;
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public URI getBackchannelLogoutUri() {
      return this.backchannelLogoutUri;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class BackchannelLogoutImplBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private AuthorizationServerId openIdProvider;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2ClientId relyingParty;
      @SuppressFBWarnings(justification = "generated code")
      private URI backchannelLogoutUri;
      @SuppressFBWarnings(justification = "generated code")
      private String logoutToken;
      @SuppressFBWarnings(justification = "generated code")
      private Supplier<CloseableHttpClient> httpClientSupplier;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2TokenRepresentation tokenRepresentation;

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

      @SuppressFBWarnings(justification = "generated code")
      public BackchannelLogoutImpl.BackchannelLogoutImplBuilder openIdProvider(AuthorizationServerId openIdProvider) {
         this.openIdProvider = openIdProvider;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public BackchannelLogoutImpl.BackchannelLogoutImplBuilder relyingParty(OAuth2ClientId relyingParty) {
         this.relyingParty = relyingParty;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public BackchannelLogoutImpl.BackchannelLogoutImplBuilder backchannelLogoutUri(URI backchannelLogoutUri) {
         this.backchannelLogoutUri = backchannelLogoutUri;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public BackchannelLogoutImpl.BackchannelLogoutImplBuilder logoutToken(String logoutToken) {
         this.logoutToken = logoutToken;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public BackchannelLogoutImpl.BackchannelLogoutImplBuilder httpClientSupplier(Supplier<CloseableHttpClient> httpClientSupplier) {
         this.httpClientSupplier = httpClientSupplier;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public BackchannelLogoutImpl.BackchannelLogoutImplBuilder tokenRepresentation(OAuth2TokenRepresentation tokenRepresentation) {
         this.tokenRepresentation = tokenRepresentation;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public BackchannelLogoutImpl build() {
         return new BackchannelLogoutImpl(
            this.openIdProvider, this.relyingParty, this.backchannelLogoutUri, this.logoutToken, this.httpClientSupplier, this.tokenRepresentation
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "BackchannelLogoutImpl.BackchannelLogoutImplBuilder(openIdProvider="
            + this.openIdProvider
            + ", relyingParty="
            + this.relyingParty
            + ", backchannelLogoutUri="
            + this.backchannelLogoutUri
            + ", logoutToken="
            + this.logoutToken
            + ", httpClientSupplier="
            + this.httpClientSupplier
            + ", tokenRepresentation="
            + this.tokenRepresentation
            + ")";
      }
   }
}
