package com.airlock.iam.oauth2.domain.model.claims;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.user.PersistentUser;
import com.airlock.iam.elog.api.EDefaultLogType;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;

public final class ContextDataClaimValueExtractor {
   private static final ELogger LOG = ELoggerFactory.getLogger(ContextDataClaimValueExtractor.class);
   @NonNull
   private final String contextDataField;
   @NonNull
   private final String claimName;

   public Optional<Object> extract(PersistentUser persistentUser) {
      Optional<Object> contextDataValue = Optional.ofNullable(persistentUser.getContextData().get(this.contextDataField));
      if (contextDataValue.isEmpty()) {
         LOG.info(
            String.format(
               "Missing %s context data value for user %s: Failed to add claim %s.",
               LogUtil.quote(this.contextDataField),
               LogUtil.quote(persistentUser.getName()),
               LogUtil.quote(this.claimName)
            )
         );
      }

      return contextDataValue;
   }

   public Optional<String> extractString(PersistentUser persistentUser) {
      return this.extract(persistentUser, String.class);
   }

   public Optional<String> extractNotBlankString(PersistentUser persistentUser) {
      return this.extractString(persistentUser)
         .filter(
            loggingFilter(
               StringUtils::isNotBlank,
               value -> LOG.info(
                  String.format(
                     "Unable to extract claim %s. String value of context data field %s is blank",
                     LogUtil.quote(this.claimName),
                     LogUtil.quote(this.contextDataField)
                  )
               )
            )
         );
   }

   public <T> Optional<T> extract(PersistentUser persistentUser, Class<T> expectedType) {
      return this.extract(persistentUser)
         .filter(
            loggingFilter(
               expectedType::isInstance,
               value -> LOG.warn(
                  EDefaultLogType.GENERIC_LOG_TYPE,
                  String.format(
                     "Unable to extract claim %s. Context data field %s is not of type %s but of %s",
                     LogUtil.quote(this.claimName),
                     LogUtil.quote(this.contextDataField),
                     LogUtil.quote(expectedType.getSimpleName()),
                     LogUtil.quote(value.getClass().getSimpleName())
                  )
               )
            )
         )
         .map(expectedType::cast);
   }

   private static <T> Predicate<T> loggingFilter(Predicate<T> predicate, Consumer<T> negativeAction) {
      return element -> {
         boolean result = predicate.test(element);
         if (!result) {
            negativeAction.accept(element);
         }

         return result;
      };
   }

   @SuppressFBWarnings(justification = "generated code")
   public ContextDataClaimValueExtractor(@NonNull String contextDataField, @NonNull String claimName) {
      if (contextDataField == null) {
         throw new NullPointerException("contextDataField is marked non-null but is null");
      }

      if (claimName == null) {
         throw new NullPointerException("claimName is marked non-null but is null");
      }

      this.contextDataField = contextDataField;
      this.claimName = claimName;
   }
}
