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

import com.airlock.iam.authentication.application.configuration.AuthorizationFlowConfig;
import com.airlock.iam.authentication.application.configuration.OneShotAuthenticationFlowConfig;
import com.airlock.iam.authentication.application.configuration.idpropagation.IdentityPropagationConfig;
import com.airlock.iam.common.api.application.configuration.valueprovider.UsernameToPropagateProviderConfig;
import com.airlock.iam.common.application.configuration.valueprovider.UserPrincipalNameProviderConfig;
import com.airlock.iam.flow.api.domain.model.flow.ApplicationId;
import com.airlock.iam.flow.application.configuration.validation.FlowStepIdConfigValidators;
import com.airlock.iam.login.api.application.configuration.targetapp.URIBasedApplicationSelectorConfig;
import com.airlock.iam.login.application.configuration.targetapp.ApplicationIdConfig;
import com.airlock.iam.login.application.configuration.targetapp.WafCredentialProviderConfig;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
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 com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.Environment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.ConfigurationException;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
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.Optional;

@Plugin(
   description = "<p>Target application for the flow-based one-shot access endpoint.</p><p>This is used for the \"One-Shot\" authentication flow of Airlock Gateway. The denied access URL is <code>/{entry path}/rest/public/authentication/one-shot/applications/{application id}</code>.</p>",
   displayName = "One-Shot Target Application"
)
public class OneShotTargetApplicationConfig implements TargetApplicationConfigSpec {
   private static final String APPLICATION_SELECTOR_DISPLAY_NAME = "Application Selector";
   @PluginProperty(
      description = "Unique ID of this application. This ID is used in the \"access\" endpoint to select the\ntarget application.",
      displayName = "Application ID",
      optional = false
   )
   private ApplicationIdConfig applicationId;
   @PluginProperty(
      description = "The authentication flow determines the user identity and awards tags to the user session for\ncompleted authentication steps. After successful completion of the authentication flow, the user is\nconsidered identified and the tags are awarded and identity propagation will be performed.",
      optional = false
   )
   private OneShotAuthenticationFlowConfig authenticationFlow;
   @PluginListProperty(
      description = "<p>Configurable response mappers to specifiy HTTP status code, headers and one-shot workflow for\nnon-success responses (i.e. flow results where the flow has not competed successfully).</p>\n<p>If no mapper is configured or none matches the flow result, a default response mapper is used: if the flow result\nspecifies a <tt>WWW-Authenticate</tt> header, a 401 status and the specified header are sent to the client\n(workflow <tt>FINAL_RESPONSE</tt>), otherwise a 403 response and workflow <tt>CONTINUE</tt> are returned.\nFor typical use cases, this should be sufficient.</p>",
      optional = true
   )
   private List<OneShotResponseMapperConfig> responseMappers;
   @PluginListProperty(
      description = "<p>These are the Airlock Gateway roles (credentials) that are set after the authentication flow\nhas successfully been completed.\nThese roles are used on the Gateway to control the access to protected backends.\nIf backend applications need user roles, those must be configured in the identity propagators.</p>\n<p>Depending on the Gateway Settings, these roles either replace the current Gateway roles\nor are added to them (default).</p>",
      optional = true
   )
   private List<WafCredentialProviderConfig> airlockGatewayRoles;
   @PluginListProperty(
      description = "Identity propagators add cookies and/or headers to the response after successful\nauthentication. The identity propagators are applied in the order in which they are configured.",
      displayName = "Identity Propagation",
      optional = true
   )
   private List<IdentityPropagationConfig> identityPropagation;
   public static final String IDENTITY_PROPAGATION_DISPLAY_NAME = "Identity Propagation";
   @PluginProperty(
      description = "Provides a value to be propagated to the backend applications by the identity propagators.",
      optional = true,
      defaultValue = UserPrincipalNameProviderConfig.class
   )
   private UsernameToPropagateProviderConfig usernameToPropagateProvider;

   public ApplicationIdConfig getId() {
      return this.applicationId;
   }

   @Override
   public ApplicationId getApplicationId() {
      return this.applicationId.getId();
   }

   @Override
   public URIBasedApplicationSelectorConfig getApplicationSelector() {
      return null;
   }

   @Override
   public Optional<AuthorizationFlowConfig> getAuthorizationFlow() {
      return Optional.empty();
   }

   public List<OneShotResponseMapperConfig> getResponseMappers() {
      return this.responseMappers;
   }

   public OneShotAuthenticationFlowConfig getAuthenticationFlow() {
      return this.authenticationFlow;
   }

   @Override
   public List<WafCredentialProviderConfig> getAirlockGatewayRoles() {
      return this.airlockGatewayRoles;
   }

   @Override
   public List<IdentityPropagationConfig> getIdentityPropagation() {
      return this.identityPropagation;
   }

   @Override
   public UsernameToPropagateProviderConfig getUsernameToPropagateProvider() {
      return this.usernameToPropagateProvider;
   }

   @ConfigurationValidator
   static void validateFlowConstraints(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      new FlowStepIdConfigValidators(
            config, env, context, true, PluginPropertyValueReader.effectivePluginPropertyValue(config, "authenticationFlow", env, context)
         )
         .validateFlowConstraints();
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public OneShotTargetApplicationConfig(
      ApplicationIdConfig applicationId,
      OneShotAuthenticationFlowConfig authenticationFlow,
      List<OneShotResponseMapperConfig> responseMappers,
      List<WafCredentialProviderConfig> airlockGatewayRoles,
      List<IdentityPropagationConfig> identityPropagation,
      UsernameToPropagateProviderConfig usernameToPropagateProvider
   ) {
      this.applicationId = applicationId;
      this.authenticationFlow = authenticationFlow;
      this.responseMappers = responseMappers;
      this.airlockGatewayRoles = airlockGatewayRoles;
      this.identityPropagation = identityPropagation;
      this.usernameToPropagateProvider = usernameToPropagateProvider;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class OneShotTargetApplicationConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private ApplicationIdConfig applicationId;
      @SuppressFBWarnings(justification = "generated code")
      private OneShotAuthenticationFlowConfig authenticationFlow;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<OneShotResponseMapperConfig> responseMappers;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<WafCredentialProviderConfig> airlockGatewayRoles;
      @SuppressFBWarnings(justification = "generated code")
      private List<IdentityPropagationConfig> identityPropagation;
      @SuppressFBWarnings(justification = "generated code")
      private UsernameToPropagateProviderConfig usernameToPropagateProvider;

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

      @SuppressFBWarnings(justification = "generated code")
      public OneShotTargetApplicationConfig.OneShotTargetApplicationConfigBuilder applicationId(ApplicationIdConfig applicationId) {
         this.applicationId = applicationId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotTargetApplicationConfig.OneShotTargetApplicationConfigBuilder authenticationFlow(OneShotAuthenticationFlowConfig authenticationFlow) {
         this.authenticationFlow = authenticationFlow;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotTargetApplicationConfig.OneShotTargetApplicationConfigBuilder responseMapper(OneShotResponseMapperConfig responseMapper) {
         if (this.responseMappers == null) {
            this.responseMappers = new ArrayList<>();
         }

         this.responseMappers.add(responseMapper);
         return this;
      }

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public OneShotTargetApplicationConfig.OneShotTargetApplicationConfigBuilder clearResponseMappers() {
         if (this.responseMappers != null) {
            this.responseMappers.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotTargetApplicationConfig.OneShotTargetApplicationConfigBuilder airlockGatewayRole(WafCredentialProviderConfig airlockGatewayRole) {
         if (this.airlockGatewayRoles == null) {
            this.airlockGatewayRoles = new ArrayList<>();
         }

         this.airlockGatewayRoles.add(airlockGatewayRole);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotTargetApplicationConfig.OneShotTargetApplicationConfigBuilder airlockGatewayRoles(
         Collection<? extends WafCredentialProviderConfig> airlockGatewayRoles
      ) {
         if (airlockGatewayRoles == null) {
            throw new NullPointerException("airlockGatewayRoles cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public OneShotTargetApplicationConfig.OneShotTargetApplicationConfigBuilder clearAirlockGatewayRoles() {
         if (this.airlockGatewayRoles != null) {
            this.airlockGatewayRoles.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotTargetApplicationConfig.OneShotTargetApplicationConfigBuilder identityPropagation(List<IdentityPropagationConfig> identityPropagation) {
         this.identityPropagation = identityPropagation;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotTargetApplicationConfig.OneShotTargetApplicationConfigBuilder usernameToPropagateProvider(
         UsernameToPropagateProviderConfig usernameToPropagateProvider
      ) {
         this.usernameToPropagateProvider = usernameToPropagateProvider;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OneShotTargetApplicationConfig build() {
         List responseMappers = switch (this.responseMappers == null ? 0 : this.responseMappers.size()) {
            case 0 -> Collections.emptyList();
            case 1 -> Collections.singletonList(this.responseMappers.get(0));
            default -> Collections.unmodifiableList(new ArrayList<>(this.responseMappers));
         };

         return new OneShotTargetApplicationConfig(
            this.applicationId, this.authenticationFlow, responseMappers, switch (this.airlockGatewayRoles == null ? 0 : this.airlockGatewayRoles.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.airlockGatewayRoles.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.airlockGatewayRoles));
            }, this.identityPropagation, this.usernameToPropagateProvider
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OneShotTargetApplicationConfig.OneShotTargetApplicationConfigBuilder(applicationId="
            + this.applicationId
            + ", authenticationFlow="
            + this.authenticationFlow
            + ", responseMappers="
            + this.responseMappers
            + ", airlockGatewayRoles="
            + this.airlockGatewayRoles
            + ", identityPropagation="
            + this.identityPropagation
            + ", usernameToPropagateProvider="
            + this.usernameToPropagateProvider
            + ")";
      }
   }
}
