package com.airlock.iam.common.domain.model.contextdata;

import com.airlock.iam.base.api.domain.model.validation.GenericError;
import com.airlock.iam.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.base.api.domain.model.validation.ValidatorError;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.contextdata.ContextData;
import com.airlock.iam.common.api.domain.model.contextdata.ContextDataItem;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.google.common.base.Joiner;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

public class ContextDataValidator {
   private static final ELogger LOG = ELoggerFactory.getLogger(ContextDataValidator.class);
   private Set<String> requiredItemKeys;

   public ValidationResult validate(ContextData previousContextData, ContextData newContextData) {
      return this.validate(previousContextData, newContextData, true);
   }

   public ValidationResult validate(ContextData previousContextData, ContextData newContextData, boolean detectMissingAttributes) {
      ValidationResult result = ValidationResult.valid();

      for (ContextDataItem<?> item : newContextData.getItems()) {
         result.mergeWith(this.validateItem(previousContextData.find(item.getKey()), item));
      }

      if (detectMissingAttributes) {
         result.mergeWith(this.getMissingRequiredItems(previousContextData, newContextData));
      }

      return result;
   }

   private ValidationResult getMissingRequiredItems(ContextData previousContextData, ContextData newContextData) {
      Set<String> requiredButMissing = this.requiredItemKeys
         .stream()
         .filter(key -> this.isMissing(key, previousContextData, newContextData))
         .collect(Collectors.toSet());
      ValidationResult validation = ValidationResult.valid();
      requiredButMissing.forEach(key -> validation.mergeWith(ValidationResult.singleError(key, GenericError.REQUIRED)));
      return validation;
   }

   private boolean isMissing(String key, ContextData previousContextData, ContextData newContextData) {
      return !newContextData.find(key).isPresent() && !previousContextData.find(key).isPresent();
   }

   private <T> ValidationResult validateItem(Optional<ContextDataItem<T>> previousItem, ContextDataItem<T> item) {
      ValidationResult result = ValidationResult.valid();
      if (!item.isEditable() && previousItem.isPresent()) {
         result.mergeWith(ValidationResult.singleError(item.getKey(), GenericError.READ_ONLY));
      }

      result.mergeWith(item.validate(previousItem));
      if (!result.isValid()) {
         this.logContextDataValidationError(item, result);
      }

      return result;
   }

   private void logContextDataValidationError(ContextDataItem<?> item, ValidationResult result) {
      String errors = Joiner.on(", ").join(result.getErrors().stream().map(ValidatorError::getMessage).collect(Collectors.toList()));
      LOG.debug("Validation of context-data field " + LogUtil.quote(item.getKey()) + " failed: " + errors);
   }

   @SuppressFBWarnings(justification = "generated code")
   public ContextDataValidator(Set<String> requiredItemKeys) {
      this.requiredItemKeys = requiredItemKeys;
   }
}
