package com.airlock.iam.login.app.misc.configuration.oneshot;

import com.airlock.iam.core.misc.util.airlock.AirlockWAFEnvValues;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.login.app.misc.LogTypes;
import com.airlock.iam.login.app.misc.configuration.UserSpecificRoleTimeoutDefinition;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.property.PluginListProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;

@Plugin(
   description = "<p>Configures the end-point used to authenticated HTTP requests with the Airlock Gateway (WAF)'s one-shot flow. It can be used, for example, to authenticate stateless REST calls.</p><p>The HTTP header of non-authenticated request are sent to this IAM end-point by the Airlock Gateway.<br/>Make sure to use \".../login-oneshot/\" as denied access URL on the corresponding gateway mappings.</p><p>This end-point may roughly do the following with the requests:<ul> <li>Extract a credential from the HTTP header (e.g. a bearer token or a cookie).</li> <li>Call an Authenticator plugin with the credential (e.g. verify the bearer token).</li> <li>Authenticate the request on the Airlock Gateway and append ID propagation information for the target application/service.</li> <li>Respond as expected by the HTTP client in all cases(e.g. use expected HTTP response code).</li></ul></p><p>All settings are configured in the list of target applications/services (see properties).<br/>The target application/service is selected by the original request by the HTTP client (provided to IAM in various environment cookies).<br/>In case no target application/service matches, the default target application/service is used.</p>",
   displayName = "One-Shot Authentication Settings"
)
public final class OneShotAuthenticationConfig {
   private static final ELogger LOG = ELoggerFactory.getLogger(OneShotAuthenticationConfig.class);
   @PluginProperty(
      description = "The default target application/service to use in case no other target application/service matches. Note that the \"URL Pattern\" property is irrelevant for this target application/service.",
      displayName = "Default Target Application/Service",
      optional = false
   )
   private OneShotTargetApplication defaultTargetApplication;
   @PluginListProperty(
      description = "<p>List of target applications/services:</p><p>One of the target applications/services is selected by matching the \"URL Pattern\" against URL of the original HTTP request sent by the client (provided to IAM by several environment cookies).<p>They are matched in the order they are declared and the first matching is used. In case no one matches, the default target application/service is used.</p>",
      displayName = "Target Application/Service",
      optional = true
   )
   private List<OneShotTargetApplication> targetApplications;
   @PluginListProperty(
      description = "Configuration of user-specific idle-timeout and role life-time values per role.",
      optional = true,
      minElements = 1,
      displayName = "User-specific Role-Timeouts"
   )
   private List<UserSpecificRoleTimeoutDefinition> userSpecificRoleTimeouts;
   public static final String USER_SPECIFIC_ROLE_TIMEOUTS_FIELD = "userSpecificRoleTimeouts";

   public OneShotTargetApplication selectTargetApplication(AirlockWAFEnvValues envValues) {
      Optional<String> accessedURL = extractAccessedURL(envValues);
      if (accessedURL.isEmpty()) {
         LOG.info(LogTypes.AIRLOCK_ONESHOT, "Cannot determine One-Shot target application, because ENV-cookie values cannot be interpreted. Using default.");
         return this.defaultTargetApplication;
      } else {
         return this.selectTargetApplicationByAccessedURL(accessedURL.get());
      }
   }

   public static Optional<String> extractAccessedURL(AirlockWAFEnvValues envValues) {
      try {
         return envValues.getUrlFromAirlockEnvironmentCookie();
      } catch (MalformedURLException | URISyntaxException e) {
         LOG.warn(LogTypes.AIRLOCK_ONESHOT, "Exception while extracting accessed URL from ENV cookies ", e);
         return Optional.empty();
      }
   }

   private OneShotTargetApplication selectTargetApplicationByAccessedURL(String accessedURL) {
      for (OneShotTargetApplication targetApp : this.targetApplications) {
         Matcher accessedURLMatcher = targetApp.getUrlPattern().matcher(accessedURL);
         if (accessedURLMatcher.matches()) {
            return targetApp;
         }
      }

      return this.defaultTargetApplication;
   }

   public OneShotTargetApplication getDefaultTargetApplication() {
      return this.defaultTargetApplication;
   }

   public List<OneShotTargetApplication> getTargetApplications() {
      return this.targetApplications;
   }

   public List<UserSpecificRoleTimeoutDefinition> getUserSpecificRoleTimeouts() {
      return this.userSpecificRoleTimeouts;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public OneShotAuthenticationConfig(
      OneShotTargetApplication defaultTargetApplication,
      List<OneShotTargetApplication> targetApplications,
      List<UserSpecificRoleTimeoutDefinition> userSpecificRoleTimeouts
   ) {
      this.defaultTargetApplication = defaultTargetApplication;
      this.targetApplications = targetApplications;
      this.userSpecificRoleTimeouts = userSpecificRoleTimeouts;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class OneShotAuthenticationConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private OneShotTargetApplication defaultTargetApplication;
      @SuppressFBWarnings(justification = "generated code")
      private List<OneShotTargetApplication> targetApplications;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<UserSpecificRoleTimeoutDefinition> userSpecificRoleTimeouts;

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

      @SuppressFBWarnings(justification = "generated code")
      public OneShotAuthenticationConfig.OneShotAuthenticationConfigBuilder defaultTargetApplication(OneShotTargetApplication defaultTargetApplication) {
         this.defaultTargetApplication = defaultTargetApplication;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotAuthenticationConfig.OneShotAuthenticationConfigBuilder targetApplications(List<OneShotTargetApplication> targetApplications) {
         this.targetApplications = targetApplications;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotAuthenticationConfig.OneShotAuthenticationConfigBuilder userSpecificRoleTimeout(UserSpecificRoleTimeoutDefinition userSpecificRoleTimeout) {
         if (this.userSpecificRoleTimeouts == null) {
            this.userSpecificRoleTimeouts = new ArrayList<>();
         }

         this.userSpecificRoleTimeouts.add(userSpecificRoleTimeout);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotAuthenticationConfig.OneShotAuthenticationConfigBuilder userSpecificRoleTimeouts(
         Collection<? extends UserSpecificRoleTimeoutDefinition> userSpecificRoleTimeouts
      ) {
         if (userSpecificRoleTimeouts == null) {
            throw new NullPointerException("userSpecificRoleTimeouts cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public OneShotAuthenticationConfig.OneShotAuthenticationConfigBuilder clearUserSpecificRoleTimeouts() {
         if (this.userSpecificRoleTimeouts != null) {
            this.userSpecificRoleTimeouts.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotAuthenticationConfig build() {
         return new OneShotAuthenticationConfig(
            this.defaultTargetApplication, this.targetApplications, switch (this.userSpecificRoleTimeouts == null ? 0 : this.userSpecificRoleTimeouts.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.userSpecificRoleTimeouts.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.userSpecificRoleTimeouts));
            }
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OneShotAuthenticationConfig.OneShotAuthenticationConfigBuilder(defaultTargetApplication="
            + this.defaultTargetApplication
            + ", targetApplications="
            + this.targetApplications
            + ", userSpecificRoleTimeouts="
            + this.userSpecificRoleTimeouts
            + ")";
      }
   }
}
