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

import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
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.DateAndTimeRangeValidationInfo;
import com.airlock.iam.base.api.domain.model.validation.info.ValidationInfo;
import com.airlock.iam.base.misc.date.Dates;
import com.airlock.iam.base.misc.time.TimeProvider;
import com.airlock.iam.common.application.configuration.validation.DefaultDateAndTimeValidatorConfig;
import com.airlock.iam.core.misc.util.DateRange;
import jakarta.inject.Inject;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Optional;

@LogicForConfig(DefaultDateAndTimeValidatorConfig.class)
public final class DefaultDateAndTimeValidator implements Validator<Date> {
   private final DefaultDateAndTimeValidatorConfig config;
   private final TimeProvider timeProvider;

   @Inject
   public DefaultDateAndTimeValidator(DefaultDateAndTimeValidatorConfig config, TimeProvider timeProvider) {
      this.config = config;
      this.timeProvider = timeProvider;
   }

   public ValidationResult validate(Date input) {
      if (input == null) {
         return ValidationResult.invalid(ValidatorError.validatorError(GenericError.REQUIRED));
      }

      DateRange dateRange = this.createRange();
      return dateRange.validateInInclusiveRange(Dates.toInstant(input));
   }

   private DateRange createRange() {
      DateRange.DateRangeBuilder result = DateRange.builder();
      Instant now = this.timeProvider.getCurrentInstant();
      this.lowerBound(now).ifPresent(result::setFromInstant);
      this.upperBound(now).ifPresent(result::setToInstant);
      return result.build();
   }

   private Optional<Instant> lowerBound(Instant now) {
      Optional<Duration> duration = this.config.getMinRelative();
      return duration.isPresent() ? Optional.of(now.plus(duration.get())) : this.config.getMinDate();
   }

   private Optional<Instant> upperBound(Instant now) {
      Optional<Duration> duration = this.config.getMaxRelative();
      return duration.isPresent() ? Optional.of(now.plus(duration.get())) : this.config.getMaxDate();
   }

   @Override
   public List<ValidationInfo> getInfo() {
      Instant now = this.timeProvider.getCurrentInstant();
      Optional<Instant> min = this.lowerBound(now);
      Optional<Instant> max = this.upperBound(now);
      if (min.isEmpty() && max.isEmpty()) {
         return List.of();
      }

      ValidationInfo info;
      if (min.isPresent() && max.isPresent()) {
         info = DateAndTimeRangeValidationInfo.createForRange(min.get(), max.get());
      } else if (min.isPresent()) {
         info = DateAndTimeRangeValidationInfo.createForMin(min.get());
      } else {
         info = DateAndTimeRangeValidationInfo.createForMax(max.get());
      }

      return List.of(info);
   }
}
