package com.airlock.iam.authentication.application.configuration.targetapp;

import com.airlock.iam.common.application.service.session.AirlockControlCookieService;
import com.airlock.iam.plugin.api.annotation.config.Groups;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.license.PluginLicenseRestriction;
import com.airlock.iam.plugin.api.annotation.property.EnumProperty;
import com.airlock.iam.plugin.api.annotation.property.GroupProperty;
import com.airlock.iam.plugin.api.annotation.property.IntegerProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginListProperty;
import com.airlock.iam.plugin.api.annotation.property.RegexProperty;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

@Plugin(
   description = "Defines status code and headers of one-shot responses. This mapper is applied if both the \"Error Code Matcher\" and the \"Next Step Matcher\" match the flow result.",
   displayName = "One-Shot Response Mapper"
)
@PluginLicenseRestriction(moduleAffiliation = "OneShotAuthentication")
@Groups(groupSequence = {"Filtering", "Response Mapping"}, openGroups = {"Filtering", "Response Mapping"})
public final class OneShotResponseMapperConfig {
   static final String FILTERING_GROUP = "Filtering";
   static final String RESPONSE_GROUP = "Response Mapping";
   @RegexProperty(
      description = "Pattern to match the error code of the response. The absence of an error code is\nrepresented as the empty string and can be matched using the pattern \"^$\". Conversly,\na non-empty error code can be matched using the pattern \".+\".",
      defaultValue = ".*",
      exampleValues = {".*", "^$", ".+", "NOT_AUTHORIZED"},
      optional = true
   )
   @GroupProperty(groupIdentifier = "Filtering")
   private Pattern errorCodeMatcher;
   @RegexProperty(
      description = "Pattern to match the next step of the response. The absence of a next step is\nrepresented as the empty string and can be matched using the pattern \"^$\".\nConversly, a non-empty error code can be matched using the pattern \".+\".",
      defaultValue = ".*",
      exampleValues = {".*", "^$", ".+", "ACCESS_CALL_REQUIRED"},
      optional = true
   )
   @GroupProperty(groupIdentifier = "Filtering")
   private Pattern nextStepMatcher;
   @IntegerProperty(
      description = "The HTTP status code to set on the response.",
      displayName = "HTTP Status Code",
      exampleValues = {401, 403, 404},
      defaultValue = 401,
      optional = true
   )
   @GroupProperty(groupIdentifier = "Response Mapping")
   private int httpStatusCode;
   @PluginListProperty(description = "Headers to be added to the response. Only headers with non-blank value are added.", optional = true)
   @GroupProperty(groupIdentifier = "Response Mapping")
   private List<OneShotResponseHeaderConfig> headers;
   @EnumProperty(
      description = "Controls the Airlock Gateway (WAF) workflow:\n<ul>\n<li><b>CONTINUE:</b> the Gateway interprets the one-shot response (e.g. adds roles/credentials etc) and then\nre-evaluates access to the requested URL and either lets the request pass or answers with the standard 403 response.</li>\n<li><b>FINAL_RESPONSE:</b> (default) the response of the one-shot request is directly passed to the client, including added headers\n(e.g. <tt>WWW-Authenticate</tt> header).</li>\n</ul>",
      defaultValue = "FINAL_RESPONSE",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Response Mapping")
   private AirlockControlCookieService.OneShotWorkflowAction workflow;

   public Pattern getErrorCodeMatcher() {
      return this.errorCodeMatcher;
   }

   public Pattern getNextStepMatcher() {
      return this.nextStepMatcher;
   }

   public int getHttpStatusCode() {
      return this.httpStatusCode;
   }

   public List<OneShotResponseHeaderConfig> getHeaders() {
      return this.headers;
   }

   public AirlockControlCookieService.OneShotWorkflowAction getWorkflow() {
      return this.workflow;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public OneShotResponseMapperConfig(
      Pattern errorCodeMatcher,
      Pattern nextStepMatcher,
      int httpStatusCode,
      List<OneShotResponseHeaderConfig> headers,
      AirlockControlCookieService.OneShotWorkflowAction workflow
   ) {
      this.errorCodeMatcher = errorCodeMatcher;
      this.nextStepMatcher = nextStepMatcher;
      this.httpStatusCode = httpStatusCode;
      this.headers = headers;
      this.workflow = workflow;
   }

   @SuppressFBWarnings(justification = "generated code")
   public OneShotResponseMapperConfig() {
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class OneShotResponseMapperConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Pattern errorCodeMatcher;
      @SuppressFBWarnings(justification = "generated code")
      private Pattern nextStepMatcher;
      @SuppressFBWarnings(justification = "generated code")
      private int httpStatusCode;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<OneShotResponseHeaderConfig> headers;
      @SuppressFBWarnings(justification = "generated code")
      private AirlockControlCookieService.OneShotWorkflowAction workflow;

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

      @SuppressFBWarnings(justification = "generated code")
      public OneShotResponseMapperConfig.OneShotResponseMapperConfigBuilder errorCodeMatcher(Pattern errorCodeMatcher) {
         this.errorCodeMatcher = errorCodeMatcher;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotResponseMapperConfig.OneShotResponseMapperConfigBuilder nextStepMatcher(Pattern nextStepMatcher) {
         this.nextStepMatcher = nextStepMatcher;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotResponseMapperConfig.OneShotResponseMapperConfigBuilder httpStatusCode(int httpStatusCode) {
         this.httpStatusCode = httpStatusCode;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotResponseMapperConfig.OneShotResponseMapperConfigBuilder header(OneShotResponseHeaderConfig header) {
         if (this.headers == null) {
            this.headers = new ArrayList<>();
         }

         this.headers.add(header);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotResponseMapperConfig.OneShotResponseMapperConfigBuilder headers(Collection<? extends OneShotResponseHeaderConfig> headers) {
         if (headers == null) {
            throw new NullPointerException("headers cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public OneShotResponseMapperConfig.OneShotResponseMapperConfigBuilder clearHeaders() {
         if (this.headers != null) {
            this.headers.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotResponseMapperConfig.OneShotResponseMapperConfigBuilder workflow(AirlockControlCookieService.OneShotWorkflowAction workflow) {
         this.workflow = workflow;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotResponseMapperConfig build() {
         return new OneShotResponseMapperConfig(
            this.errorCodeMatcher, this.nextStepMatcher, this.httpStatusCode, switch (this.headers == null ? 0 : this.headers.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.headers.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.headers));
            }, this.workflow
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OneShotResponseMapperConfig.OneShotResponseMapperConfigBuilder(errorCodeMatcher="
            + this.errorCodeMatcher
            + ", nextStepMatcher="
            + this.nextStepMatcher
            + ", httpStatusCode="
            + this.httpStatusCode
            + ", headers="
            + this.headers
            + ", workflow="
            + this.workflow
            + ")";
      }
   }
}
