package com.airlock.iam.oauth2.domain.model.client.communication;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.misc.json.JsonCoding;
import com.airlock.iam.core.domain.model.http.HttpRequest;
import com.airlock.iam.core.domain.model.http.HttpRequestHeader;
import com.airlock.iam.core.domain.model.http.HttpRequestParameter;
import com.airlock.iam.core.domain.model.http.HttpResponse;
import com.airlock.iam.core.domain.model.http.client.HttpClient;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.login.api.domain.model.oauth2.AccessToken;
import com.airlock.iam.oauth2.domain.model.client.OAuth2RequestBuilder;
import com.airlock.iam.oauth2.domain.service.client.resource.FailedResourceDataCollector;
import com.airlock.iam.oauth2.domain.service.client.resource.OAuth2RemoteResource;
import com.airlock.iam.oauth2.domain.service.client.resource.OAuth2ResourceDataCollector;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;

public final class OAuth2ClientResourceRequestLogic {
   private final ELogger logger;
   private final HttpClient httpClientInstance;
   private final OAuth2ResourceRequestSecretProcessor secretProcessor;
   private final URI resourceURI;
   private final OAuth2RequestBuilder requestMethod;
   private final List<OAuth2RemoteResource> resources;

   public OAuth2ResourceDataCollector collectResources(AccessToken accessToken, OAuth2ResourceDataCollector collector) {
      Optional<Map<String, Object>> resourceData = this.executeResourceRequest(accessToken);
      if (resourceData.isPresent()) {
         for (OAuth2RemoteResource resource : this.resources) {
            collector = collector.collectorWithMergedData(resource.collectResource(resourceData.get()));
            if (!collector.isSuccessful()) {
               this.logger.info("Unable to collect resource in resource request to '" + this.resourceURI.toString() + "'.");
               return collector;
            }
         }

         return collector;
      } else {
         return FailedResourceDataCollector.failedResourceDataCollector();
      }
   }

   private Optional<Map<String, Object>> executeResourceRequest(AccessToken accessToken) {
      try {
         HttpRequest resourceRequest = this.buildResourceRequest(accessToken);
         this.logger.debug("Executing resource request to \"" + this.resourceURI.toString() + "\".");
         HttpResponse resourceResponse = this.httpClientInstance.execute(resourceRequest);
         this.logger.debug("Resource response for request \"" + this.resourceURI.toString() + "\" received.");
         return this.parseResourceResponse(resourceResponse);
      } catch (FailureException e) {
         this.logger.debug("OAuth 2.0 resource request failed. Cause: " + e.getMessage(), e);
         return Optional.empty();
      }
   }

   private HttpRequest buildResourceRequest(AccessToken accessToken) {
      List<HttpRequestParameter> params = new ArrayList<>();
      List<HttpRequestHeader> headers = new ArrayList<>();
      this.secretProcessor.putAccessTokenInRequestData(accessToken, params, headers);
      return this.requestMethod.buildRequestWithParams(this.resourceURI, params, headers);
   }

   private Optional<Map<String, Object>> parseResourceResponse(HttpResponse resourceResponse) {
      if (StringUtils.isBlank(resourceResponse.body())) {
         this.logger.info("Failed to parse resource response body.");
         return Optional.empty();
      } else {
         String resourceJSON = resourceResponse.body();
         this.logger.debug("Received resource response: " + resourceJSON);
         Optional<Map<String, Object>> optionalResponse = JsonCoding.tryMapFromJson(resourceJSON);
         if (optionalResponse.isEmpty()) {
            this.logger.info("Failed to parse resource response body.");
            return Optional.empty();
         } else {
            Map<String, Object> response = optionalResponse.get();
            this.logger.debug("Resource response as JSON:\n" + JsonCoding.toPrettyJson(response));
            return Optional.of(response);
         }
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   OAuth2ClientResourceRequestLogic(
      ELogger logger,
      HttpClient httpClientInstance,
      OAuth2ResourceRequestSecretProcessor secretProcessor,
      URI resourceURI,
      OAuth2RequestBuilder requestMethod,
      List<OAuth2RemoteResource> resources
   ) {
      this.logger = logger;
      this.httpClientInstance = httpClientInstance;
      this.secretProcessor = secretProcessor;
      this.resourceURI = resourceURI;
      this.requestMethod = requestMethod;
      this.resources = resources;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2ClientResourceRequestLogicBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private ELogger logger;
      @SuppressFBWarnings(justification = "generated code")
      private HttpClient httpClientInstance;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2ResourceRequestSecretProcessor secretProcessor;
      @SuppressFBWarnings(justification = "generated code")
      private URI resourceURI;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2RequestBuilder requestMethod;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<OAuth2RemoteResource> resources;

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

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientResourceRequestLogic.OAuth2ClientResourceRequestLogicBuilder logger(ELogger logger) {
         this.logger = logger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientResourceRequestLogic.OAuth2ClientResourceRequestLogicBuilder httpClientInstance(HttpClient httpClientInstance) {
         this.httpClientInstance = httpClientInstance;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientResourceRequestLogic.OAuth2ClientResourceRequestLogicBuilder secretProcessor(OAuth2ResourceRequestSecretProcessor secretProcessor) {
         this.secretProcessor = secretProcessor;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientResourceRequestLogic.OAuth2ClientResourceRequestLogicBuilder resourceURI(URI resourceURI) {
         this.resourceURI = resourceURI;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientResourceRequestLogic.OAuth2ClientResourceRequestLogicBuilder requestMethod(OAuth2RequestBuilder requestMethod) {
         this.requestMethod = requestMethod;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientResourceRequestLogic.OAuth2ClientResourceRequestLogicBuilder resource(OAuth2RemoteResource resource) {
         if (this.resources == null) {
            this.resources = new ArrayList<>();
         }

         this.resources.add(resource);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientResourceRequestLogic.OAuth2ClientResourceRequestLogicBuilder resources(Collection<? extends OAuth2RemoteResource> resources) {
         if (resources == null) {
            throw new NullPointerException("resources cannot be null");
         }

         if (this.resources == null) {
            this.resources = new ArrayList<>();
         }

         this.resources.addAll(resources);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientResourceRequestLogic.OAuth2ClientResourceRequestLogicBuilder clearResources() {
         if (this.resources != null) {
            this.resources.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ClientResourceRequestLogic build() {
         return new OAuth2ClientResourceRequestLogic(
            this.logger,
            this.httpClientInstance,
            this.secretProcessor,
            this.resourceURI,
            this.requestMethod,
            switch (this.resources == null ? 0 : this.resources.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.resources.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.resources));
            }
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2ClientResourceRequestLogic.OAuth2ClientResourceRequestLogicBuilder(logger="
            + this.logger
            + ", httpClientInstance="
            + this.httpClientInstance
            + ", secretProcessor="
            + this.secretProcessor
            + ", resourceURI="
            + this.resourceURI
            + ", requestMethod="
            + this.requestMethod
            + ", resources="
            + this.resources
            + ")";
      }
   }
}
