package com.airlock.iam.oauth2.domain.service.clientauthentication.clientsecret;

import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.oauth2.api.domain.model.clientauthentication.ClientAuthenticationMethod;
import com.airlock.iam.oauth2.api.domain.model.http.OAuth2HttpRequest;
import com.airlock.iam.oauth2.application.configuration.clientauthentication.OAuth2HeaderClientSecretMethodConfig;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;

@LogicForConfig(OAuth2HeaderClientSecretMethodConfig.class)
public final class OAuth2HeaderClientSecretMethod implements OAuth2ClientSecretMethod {
   private static final String CLIENT_ID_PLACEHOLDER = "$CLIENT_ID$";
   private static final String CLIENT_SECRET_PLACEHOLDER = "$CLIENT_SECRET$";
   private static final String CLIENT_ID_MATCHING_GROUP_NAME = "clientid";
   private static final String CLIENT_SECRET_MATCHING_GROUP_NAME = "clientsecret";
   private final Provider<OAuth2HeaderClientSecretMethodSettings> settings;

   @Inject
   public OAuth2HeaderClientSecretMethod(Provider<OAuth2HeaderClientSecretMethodSettings> settings) {
      this.settings = settings;
   }

   @Override
   public Optional<String> getClientIdFromRequest(OAuth2HttpRequest request) {
      return this.extractSecretInformationFromRequest(request, "$CLIENT_ID$");
   }

   @Override
   public Optional<String> getClientSecretFromRequest(OAuth2HttpRequest request) {
      return this.extractSecretInformationFromRequest(request, "$CLIENT_SECRET$");
   }

   private Optional<String> extractSecretInformationFromRequest(OAuth2HttpRequest request, String placeHolder) {
      return request.getHeader(((OAuth2HeaderClientSecretMethodSettings)this.settings.get()).headerField())
         .filter(StringUtils::isNotBlank)
         .map(this::removeHeaderPrefix)
         .map(this::decode)
         .flatMap(value -> this.resolveValueFromPlaceHolder(value, placeHolder));
   }

   private Optional<String> resolveValueFromPlaceHolder(String value, String placeHolder) {
      return ((OAuth2HeaderClientSecretMethodSettings)this.settings.get())
         .clientSecretFormat()
         .filter(StringUtils::isNotBlank)
         .map(format -> resolve(format, value, placeHolder))
         .orElse(Optional.of(value));
   }

   private static Optional<String> resolve(String format, String value, String placeHolder) {
      Matcher matcher = searchPattern(format).matcher(value);
      if (!matcher.matches()) {
         return Optional.empty();
      } else if ("$CLIENT_ID$".equals(placeHolder)) {
         return format.contains("$CLIENT_ID$") ? Optional.of(matcher.group("clientid")) : Optional.empty();
      } else {
         return Optional.of(matcher.group("clientsecret"));
      }
   }

   private static Pattern searchPattern(String format) {
      String searchPattern = Strings.CS
         .replaceOnce(format, "$CLIENT_ID$", "(?<clientid>.+)")
         .replace("$CLIENT_ID$", "\\k<clientid>")
         .replace("$CLIENT_SECRET$", "(?<clientsecret>.+)");
      return Pattern.compile(searchPattern);
   }

   private String decode(String value) {
      return ((OAuth2HeaderClientSecretMethodSettings)this.settings.get()).base64EncodeClientSecretValue()
         ? new String(Base64.decodeBase64(value), StandardCharsets.UTF_8)
         : value;
   }

   private String removeHeaderPrefix(String value) {
      return ((OAuth2HeaderClientSecretMethodSettings)this.settings.get())
         .headerPrefix()
         .filter(StringUtils::isNotBlank)
         .map(prefix -> Strings.CS.removeStart(value, prefix + " "))
         .orElse(value);
   }

   @Override
   public ClientAuthenticationMethod clientAuthenticationMethod() {
      return new ClientAuthenticationMethod() {
         @Override
         public String getIdentifier() {
            return "client_secret_custom_header";
         }
      };
   }
}
