package com.airlock.iam.login.domain.model.location.interpret;

import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.common.api.domain.model.location.transform.TransformationResult;
import com.airlock.iam.common.domain.model.transform.TransformationProcessor;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.login.api.domain.model.location.extract.URIValueExtractor;
import com.airlock.iam.login.application.configuration.location.interpret.LocationInterpreterConfig;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import java.net.URI;
import java.util.List;
import java.util.Optional;

@LogicForConfig(LocationInterpreterConfig.class)
public final class LocationInterpreter {
   private static final ELogger LOG = ELoggerFactory.getLogger(LocationInterpreter.class);
   private final Optional<String> defaultValue;
   private final TransformationProcessor<URI> uriTransformationProcessor;
   private final List<URIValueExtractor> uriValueExtractors;
   private final TransformationProcessor<String> stringTransformationProcessor;

   @Inject
   public LocationInterpreter(
      Optional<String> defaultValue,
      TransformationProcessor<URI> uriTransformationProcessor,
      List<URIValueExtractor> uriValueExtractors,
      TransformationProcessor<String> stringTransformationProcessor
   ) {
      this.defaultValue = defaultValue;
      this.uriTransformationProcessor = uriTransformationProcessor;
      this.uriValueExtractors = uriValueExtractors;
      this.stringTransformationProcessor = stringTransformationProcessor;
   }

   public Optional<String> interpret(URI uri) {
      TransformationResult<URI> uriTransformationResult = this.transformURI(uri);
      if (uriTransformationResult.isVeto()) {
         LOG.debug(String.format("Interpretation vetoed by URI transformation chain. Returning %s.", this.defaultValueAsString()));
         return this.defaultValue;
      } else {
         Optional<String> extractedValue = this.extractURIValue(uriTransformationResult.getValue());
         if (extractedValue.isEmpty()) {
            LOG.debug(String.format("Extraction produces no value. Returning default ('%s').", this.defaultValueAsString()));
            return this.defaultValue;
         } else {
            LOG.debug(String.format("Extracted value '%s' from URI", extractedValue));
            TransformationResult<String> stringTransformationResult = this.transformString(extractedValue.get());
            if (stringTransformationResult.isVeto()) {
               LOG.debug(String.format("Interpretation vetoed by String transformation chain. Returning %s.", this.defaultValueAsString()));
               return this.defaultValue;
            } else {
               return Optional.ofNullable(stringTransformationResult.getValue());
            }
         }
      }
   }

   private TransformationResult<URI> transformURI(URI uri) {
      TransformationResult<URI> result = this.uriTransformationProcessor.transformToResult(uri);
      if (result.hasValue() && !result.getValue().equals(uri)) {
         LOG.debug(String.format("Transformed URI '%s' to '%s'", uri.toString(), result.getValue().toString()));
      }

      return result;
   }

   private Optional<String> extractURIValue(URI uri) {
      if (this.uriValueExtractors.isEmpty()) {
         return Optional.of(uri.toString());
      }

      for (URIValueExtractor extractor : this.uriValueExtractors) {
         Optional<String> value = extractor.extractFrom(uri);
         if (value.isPresent()) {
            return value;
         }
      }

      return Optional.empty();
   }

   private TransformationResult<String> transformString(String value) {
      TransformationResult<String> result = this.stringTransformationProcessor.transformToResult(value);
      if (result.hasValue() && !result.getValue().equals(value)) {
         LOG.debug(String.format("Transformed extracted value to '%s'", value));
      }

      return result;
   }

   private String defaultValueAsString() {
      return this.defaultValue.isEmpty() ? "no value" : "'" + this.defaultValue.get() + "'";
   }

   @SuppressFBWarnings(justification = "generated code")
   public static LocationInterpreter.LocationInterpreterBuilder builder() {
      return new LocationInterpreter.LocationInterpreterBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class LocationInterpreterBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Optional<String> defaultValue;
      @SuppressFBWarnings(justification = "generated code")
      private TransformationProcessor<URI> uriTransformationProcessor;
      @SuppressFBWarnings(justification = "generated code")
      private List<URIValueExtractor> uriValueExtractors;
      @SuppressFBWarnings(justification = "generated code")
      private TransformationProcessor<String> stringTransformationProcessor;

      @SuppressFBWarnings(justification = "generated code")
      LocationInterpreterBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public LocationInterpreter.LocationInterpreterBuilder defaultValue(Optional<String> defaultValue) {
         this.defaultValue = defaultValue;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LocationInterpreter.LocationInterpreterBuilder uriTransformationProcessor(TransformationProcessor<URI> uriTransformationProcessor) {
         this.uriTransformationProcessor = uriTransformationProcessor;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LocationInterpreter.LocationInterpreterBuilder uriValueExtractors(List<URIValueExtractor> uriValueExtractors) {
         this.uriValueExtractors = uriValueExtractors;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LocationInterpreter.LocationInterpreterBuilder stringTransformationProcessor(TransformationProcessor<String> stringTransformationProcessor) {
         this.stringTransformationProcessor = stringTransformationProcessor;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public LocationInterpreter build() {
         return new LocationInterpreter(this.defaultValue, this.uriTransformationProcessor, this.uriValueExtractors, this.stringTransformationProcessor);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "LocationInterpreter.LocationInterpreterBuilder(defaultValue="
            + this.defaultValue
            + ", uriTransformationProcessor="
            + this.uriTransformationProcessor
            + ", uriValueExtractors="
            + this.uriValueExtractors
            + ", stringTransformationProcessor="
            + this.stringTransformationProcessor
            + ")";
      }
   }
}
