package com.airlock.iam.flow.shared.domain.model.item;

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.Validator;
import com.airlock.iam.base.api.domain.model.validation.ValidatorError;
import com.airlock.iam.base.api.domain.model.validation.info.ValidationInfo;
import com.airlock.iam.common.api.domain.model.contextdata.ContextData;
import com.airlock.iam.common.api.domain.model.user.User;
import com.airlock.iam.common.domain.model.contextdata.ContextDataImpl;
import com.airlock.iam.common.domain.model.contextdata.ContextDataItemImpl;
import com.airlock.iam.common.domain.service.validation.ValidationResultFieldNameHelper;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.shared.api.domain.model.item.UserDataItemDefinition;
import com.airlock.iam.flow.shared.application.configuration.item.ContextDataItemDefinitionConfig;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.annotation.Nonnull;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

public abstract class ContextDataItemDefinitionBase<T> implements UserDataItemDefinition<T> {
   private static final ELogger LOG = ELoggerFactory.getLogger(ContextDataItemDefinitionBase.class);
   private final ContextDataItemDefinitionConfig<T> config;
   private final List<Validator<T>> validators;

   @Override
   public ValidationResult validate(Optional<T> value) {
      if (value.isEmpty()) {
         return this.config.isRequired() ? this.createRequiredResult() : ValidationResult.valid();
      } else {
         return this.validateValue(value.get());
      }
   }

   public final boolean isRequired() {
      return this.config.isRequired();
   }

   private ValidationResult createRequiredResult() {
      return ValidationResult.invalid(ValidatorError.validatorError(this.config.getContextDataItemName(), GenericError.REQUIRED));
   }

   public final Optional<T> convert(@Nonnull Object value) {
      try {
         if (this.config.getType().isAssignableFrom(value.getClass())) {
            return Optional.of((T)value);
         }
      } catch (ClassCastException e) {
         LOG.debug(
            String.format(
               "Could not convert received value of type '%s' for item '%s' to type '%s'.",
               value.getClass(),
               this.config.getContextDataItemName(),
               this.config.getType()
            ),
            e
         );
         return Optional.empty();
      }

      Optional<T> internalConversionResult = this.convertInternal(value);
      if (internalConversionResult.isPresent()) {
         return internalConversionResult;
      }

      LOG.debug(String.format("Unrecognized type ('%s') for item '%s'.", value.getClass(), this.config.getContextDataItemName()));
      return Optional.empty();
   }

   protected abstract Optional<T> convertInternal(@Nonnull Object var1);

   public String getName() {
      return this.config.getContextDataItemName();
   }

   private ValidationResult validateValue(T value) {
      ValidationResult validationResult = ValidationResult.valid();
      this.validators.forEach(validator -> {
         ValidationResult itemValidationResult = validator.validate(value);
         validationResult.mergeWith(itemValidationResult);
      });
      return validationResult.isValid()
         ? validationResult
         : ValidationResultFieldNameHelper.setFieldName(validationResult, this.config.getContextDataItemName());
   }

   protected final List<ValidationInfo> getAllValidationInfos() {
      return this.validators.stream().flatMap(x -> x.getInfo().stream()).collect(Collectors.toList());
   }

   @Override
   public Optional<T> getValueFromUser(User user) {
      return user.getContextData().find(this.config.getContextDataItemName()).map(item -> (T)item.getValue());
   }

   @Override
   public void setValueOnUser(@Nonnull T value, User user) {
      ContextData additionalContextData = new ContextDataImpl(Set.of(new ContextDataItemImpl(this.config.getContextDataItemName(), value)));
      user.setContextData(user.getContextData().mergeWith(additionalContextData));
   }

   @Override
   public void clearValueOnUser(User user) {
      ContextData newContextData = user.getContextData().filter(data -> !data.getKey().equals(this.config.getContextDataItemName()));
      user.setContextData(newContextData);
   }

   @Override
   public String getFieldName() {
      return this.config.getContextDataItemName();
   }

   public Optional<String> getInputPurpose() {
      return this.config.getInputPurpose();
   }

   @SuppressFBWarnings(justification = "generated code")
   public ContextDataItemDefinitionBase(ContextDataItemDefinitionConfig<T> config, List<Validator<T>> validators) {
      this.config = config;
      this.validators = validators;
   }
}
