package com.airlock.iam.oauth2.domain.service.client.resource;

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.google.common.collect.ImmutableMap;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Map;
import java.util.Optional;

public final class OAuth2RemoteContextDataResourceLogic implements OAuth2RemoteResource {
   private static final ELogger LOG = ELoggerFactory.getLogger(OAuth2RemoteContextDataResourceLogic.class);
   private final String localContextDataKey;
   private final boolean optional;
   private final OAuth2ResourceSelector resourceSelector;
   private final Optional<TransformationProcessor<String>> stringTransformationProcessor;

   @Override
   public OAuth2SingleResourceData collectResource(Map<String, Object> parsedResourceResponse) {
      return this.resourceSelector.retrieveResource(parsedResourceResponse, Object.class).map(this::data).orElseGet(this::dataNotFound);
   }

   private OAuth2SingleResourceData data(Object resource) {
      return this.stringTransformationProcessor
         .<OAuth2SingleResourceData>map(processor -> this.transformStringResource((TransformationProcessor<String>)processor, resource))
         .orElse(this.resourceData(resource));
   }

   private OAuth2SingleResourceData resourceData(Object resource) {
      return DefaultOAuth2SingleResourceData.resourceDataWithContextData(ImmutableMap.of(this.localContextDataKey, resource));
   }

   private OAuth2SingleResourceData transformStringResource(TransformationProcessor<String> stringProcessor, Object resource) {
      if (resource instanceof String) {
         Optional<String> stringResourceTransformed = stringProcessor.transform((String)resource);
         return stringResourceTransformed.map(this::resourceData).orElseGet(this::stringTransformationFailed);
      } else {
         return this.stringTypeExpected(resource.getClass());
      }
   }

   private <T> OAuth2SingleResourceData stringTypeExpected(Class<T> actual) {
      LOG.info(
         String.format(
            "The resource request for context data key %s was successful, but the resource has the wrong type (expected: %s, actual: %s).",
            this.localContextDataKey,
            String.class.getCanonicalName(),
            actual.getCanonicalName()
         )
      );
      return FailedOAuth2SingleResourceData.failedResourceData();
   }

   private OAuth2SingleResourceData stringTransformationFailed() {
      LOG.info(
         "The resource request for context data key "
            + this.localContextDataKey
            + " was successful, but the resource could not be transformed using the configured string transformer chain."
      );
      return FailedOAuth2SingleResourceData.failedResourceData();
   }

   private OAuth2SingleResourceData dataNotFound() {
      if (this.optional) {
         return DefaultOAuth2SingleResourceData.resourceDataWithContextData(ImmutableMap.of());
      }

      this.logContextDataNotFound();
      return FailedOAuth2SingleResourceData.failedResourceData();
   }

   private void logContextDataNotFound() {
      LOG.info(
         "The resource request for context data key "
            + this.localContextDataKey
            + " was successful, but the resource was not found. This leads to an exception, as the field is not configured as optional."
      );
   }

   @SuppressFBWarnings(justification = "generated code")
   public OAuth2RemoteContextDataResourceLogic(
      String localContextDataKey,
      boolean optional,
      OAuth2ResourceSelector resourceSelector,
      Optional<TransformationProcessor<String>> stringTransformationProcessor
   ) {
      this.localContextDataKey = localContextDataKey;
      this.optional = optional;
      this.resourceSelector = resourceSelector;
      this.stringTransformationProcessor = stringTransformationProcessor;
   }
}
