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

import com.airlock.iam.core.misc.plugin.config.validation.UriPropertyValidator;
import com.airlock.iam.flow.api.application.configuration.MaintenanceMessageUiSettings;
import com.airlock.iam.flow.api.domain.model.flow.ApplicationId;
import com.airlock.iam.flow.api.domain.model.flow.FlowId;
import com.airlock.iam.flow.api.domain.model.step.StepAction;
import com.airlock.iam.flow.api.domain.model.step.StepId;
import com.airlock.iam.flow.ui.application.configuration.CancellationTargetConfigValidator;
import com.airlock.iam.flow.ui.application.configuration.FlowRedirectTargetConfig;
import com.airlock.iam.flow.ui.application.configuration.FlowUiConfig;
import com.airlock.iam.flow.ui.application.configuration.PublicFlowRedirectTargetConfig;
import com.airlock.iam.flow.ui.application.configuration.PublicSelfServiceFlowLinkConfig;
import com.airlock.iam.login.application.configuration.location.interpret.LocationInterpreterConfig;
import com.airlock.iam.login.application.configuration.location.propagate.TargetURIResolverConfig;
import com.airlock.iam.login.application.configuration.targetapp.ApplicationIdConfig;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
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.property.BooleanProperty;
import com.airlock.iam.plugin.api.annotation.property.GroupProperty;
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.exception.PropertyConfigurationException;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.Strings;

@Plugin(description = "User interface configuration for authentication and authorization.", displayName = "Authentication & Authorization UI")
@Groups(
   openGroups = {"Basic Settings", "On Flow Completion", "On Flow Cancellation", "On Flow Failure"},
   groupSequence = {"Basic Settings", "On Flow Completion", "On Flow Cancellation", "On Flow Failure"}
)
public final class AuthenticationUiConfig implements FlowUiConfig {
   public static final String SPA_LANG_KEY = "IAM_UI_LANGUAGE";
   @PluginProperty(
      description = "The identifier of the target application that the user interface configuration refers to.",
      displayName = "Target Application ID",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private ApplicationIdConfig targetApplicationId;
   public static final String TARGET_APPLICATION_ID = "targetApplicationId";
   @PluginListProperty(
      description = "The user interface configuration for the steps. Note: if using standard IAM steps, no user interface has to be configured manually.",
      displayName = "Customized Step UIs",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private List<AuthenticationStepUiConfig> customizedStepUis;
   public static final String CUSTOMIZED_STEP_UIS_FIELD = "customizedStepUis";
   @PluginProperty(
      description = "<p>The Language Extractor is a Location Interpreter which allows the UI to extract the language from the forward location. It is added to the value map from the location interpretation endpoint (<tt>/&lt;loginapp-uri&gt;/rest/public/authentication/location/interpret/</tt>) with the key <tt>IAM_UI_LANGUAGE</tt>.</p><p>The IAM UI uses this endpoint, when application access with a forward location is requested. For this, an \"Application Selector\" must be configured on the \"Target Application\" configuration for the respective authentication flow. Otherwise, this setting has no effect for the default IAM UI.</p>",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private LocationInterpreterConfig languageExtractor;
   @BooleanProperty(
      description = "<p>Show Goto buttons for all configured Goto targets on all pages using default UIs of this flow. Clicking a Goto button will redirect to the corresponding Goto target. The Goto targets are configured in the flows themselves, not the UIs.</p><p>For customized step UIs, Goto buttons have to be configured explicitly using the \"Goto Button UI Element\" plugin.</p><p><b>Notice:</b> Goto buttons do not come with pre-defined labels. It is required to add i18n keys and values for each button manually. The key may looks as follows: '<tt>authentication.pages.actions.goto.&lt;currentStepId&gt;.&lt;targetStepId&gt;</tt>'.</p>",
      optional = true,
      defaultValue = "true",
      displayName = "Show Goto Buttons"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private boolean showGotoButtons;
   @PluginProperty(
      description = "Settings to define if and how maintenance messages are displayed for this flow. If this property is not set no maintenance messages are displayed for this flow.",
      optional = true,
      displayName = "Maintenance Message UI Settings"
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private MaintenanceMessageUiSettings maintenanceMessageUiSettings;
   public static final String MAINTENANCE_MESSAGE_UI_SETTINGS_KEY = "maintenanceMessageUiSettings";
   @PluginProperty(
      description = "The self-unlock flow to use when a user is locked on this authentication flow. If configured, a message is displayed with a link to start this self-unlock flow. The link is displayed using the resource key: <tt>authentication.pages.messages.self-unlock</tt>",
      displayName = "Self-Unlock Flow",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private PublicSelfServiceFlowLinkConfig selfUnlockFlow;
   @PluginProperty(
      displayName = "Target URI Resolver",
      description = "Resolves the URI to be propagated to after successful authentication.<ul><li>The resolved URL must either be absolute (i.e. using https://) or start with a slash.</li><li>It may be necessary to configure 'Identity Propagation' to make the authentication work.</li><li>Uses the custom 'X-Forward-URL' header to inform the SPA about the target. Do not set the same header again in the identity propagation of the respective authentication flow.</li><li><b>Note:</b> this setting is irrelevant for SAML 2.0 target applications (a target application where the \"SAML 2.0 Identity Propagator\" is configured). For such an application, simply configure a resolver with \"/\" as default value.</li></ul>",
      optional = false
   )
   @GroupProperty(groupIdentifier = "On Flow Completion")
   private TargetURIResolverConfig targetURIResolver;
   private static final String TARGET_URI_RESOLVER_FIELD = "targetURIResolver";
   @PluginProperty(
      description = "If configured, shows a cancel button on all pages, except the first, using default UIs of this flow. Clicking the cancel button will abort the flow and redirect to the configured target.<p>For customized step UIs, cancel buttons have to be configured explicitly using the \"Cancel Button UI Element\" plugin.</p>",
      displayName = "Cancellation Target",
      optional = true
   )
   @GroupProperty(groupIdentifier = "On Flow Cancellation")
   private PublicFlowRedirectTargetConfig cancellationTarget;
   @PluginProperty(
      description = "Defines the redirect URL in the case of a failed flow. If not configured and the flow fails, the flow is restarted, unless it has failed immediately, in which case an error page is displayed.",
      displayName = "Flow Failure Target",
      optional = true
   )
   @GroupProperty(groupIdentifier = "On Flow Failure")
   private FlowRedirectTargetConfig flowFailureTarget;
   @BooleanProperty(
      description = "If enabled, displays the cancel button also on the first interactive page of the flow. This can be useful if the \"Cancellation Target\" redirects to another flow or external page. <p>Note that even if this flag is disabled, a cancel button on the first page is always shown when the first page is reached again during the flow, e.g. by a Goto.</p>",
      displayName = "Show Cancel Button On First Page",
      defaultValue = "false",
      optional = true
   )
   @GroupProperty(groupIdentifier = "On Flow Cancellation")
   private boolean showCancelButtonOnFirstPage;

   public boolean isShowGotoButtons() {
      return this.showGotoButtons;
   }

   public TargetURIResolverConfig getTargetURIResolver() {
      return this.targetURIResolver;
   }

   public Optional<MaintenanceMessageUiSettings> getMaintenanceMessageUiSettings() {
      return Optional.ofNullable(this.maintenanceMessageUiSettings);
   }

   public Optional<LocationInterpreterConfig> getLanguageExtractor() {
      return Optional.ofNullable(this.languageExtractor);
   }

   public ApplicationId getTargetApplicationId() {
      return this.targetApplicationId.getId();
   }

   public Optional<PublicSelfServiceFlowLinkConfig> getSelfUnlockFlow() {
      return Optional.ofNullable(this.selfUnlockFlow);
   }

   public Optional<AuthenticationStepUiConfig> getUiConfigMatching(StepId stepId, StepAction stepAction) {
      return this.findUiConfigMatchingSpecificStepAction(stepId, stepAction).or(() -> this.findUiConfigMatchingAllStepActions(stepId));
   }

   public Optional<FlowRedirectTargetConfig> getCancellationTarget() {
      return Optional.ofNullable(this.cancellationTarget);
   }

   public Optional<FlowRedirectTargetConfig> getFlowFailureRedirectTarget() {
      return Optional.ofNullable(this.flowFailureTarget);
   }

   public boolean showCancelButtonOnFirstInteractiveStep() {
      return this.showCancelButtonOnFirstPage;
   }

   public FlowId getFlowId() {
      return new FlowId(this.targetApplicationId.getId().getApplicationId());
   }

   private Optional<AuthenticationStepUiConfig> findUiConfigMatchingSpecificStepAction(StepId stepId, StepAction stepAction) {
      return this.customizedStepUis
         .stream()
         .filter(c -> c.getRequiredStepAction().isPresent())
         .filter(c -> stepId.equals(c.getStepId()) && Strings.CS.equals(stepAction.name(), ((StepAction)c.getRequiredStepAction().get()).name()))
         .findFirst();
   }

   private Optional<AuthenticationStepUiConfig> findUiConfigMatchingAllStepActions(StepId stepId) {
      return this.customizedStepUis.stream().filter(c -> c.getRequiredStepAction().isEmpty()).filter(c -> stepId.equals(c.getStepId())).findFirst();
   }

   @ConfigurationValidator
   static void validateCancellationTarget(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      new CancellationTargetConfigValidator(config, env, context).validate();
   }

   @ConfigurationValidator
   static void validateDefaultTargetUri(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws PropertyConfigurationException {
      PluginConfiguration targetUriConfig = PluginPropertyValueReader.effectivePluginPropertyValue(config, "targetURIResolver", env, context);
      if (targetUriConfig != null) {
         UriPropertyValidator.uriPropertyValidator("defaultValue", "Default Value", targetUriConfig, env, context).validateIsAbsoluteOrStartsWithSlash();
      }
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public AuthenticationUiConfig.AuthenticationUiConfigBuilder toBuilder() {
      return new AuthenticationUiConfig.AuthenticationUiConfigBuilder()
         .targetApplicationId(this.targetApplicationId)
         .customizedStepUis(this.customizedStepUis)
         .languageExtractor(this.languageExtractor)
         .showGotoButtons(this.showGotoButtons)
         .maintenanceMessageUiSettings(this.maintenanceMessageUiSettings)
         .selfUnlockFlow(this.selfUnlockFlow)
         .targetURIResolver(this.targetURIResolver)
         .cancellationTarget(this.cancellationTarget)
         .flowFailureTarget(this.flowFailureTarget)
         .showCancelButtonOnFirstPage(this.showCancelButtonOnFirstPage);
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public AuthenticationUiConfig(
      ApplicationIdConfig targetApplicationId,
      List<AuthenticationStepUiConfig> customizedStepUis,
      LocationInterpreterConfig languageExtractor,
      boolean showGotoButtons,
      MaintenanceMessageUiSettings maintenanceMessageUiSettings,
      PublicSelfServiceFlowLinkConfig selfUnlockFlow,
      TargetURIResolverConfig targetURIResolver,
      PublicFlowRedirectTargetConfig cancellationTarget,
      FlowRedirectTargetConfig flowFailureTarget,
      boolean showCancelButtonOnFirstPage
   ) {
      this.targetApplicationId = targetApplicationId;
      this.customizedStepUis = customizedStepUis;
      this.languageExtractor = languageExtractor;
      this.showGotoButtons = showGotoButtons;
      this.maintenanceMessageUiSettings = maintenanceMessageUiSettings;
      this.selfUnlockFlow = selfUnlockFlow;
      this.targetURIResolver = targetURIResolver;
      this.cancellationTarget = cancellationTarget;
      this.flowFailureTarget = flowFailureTarget;
      this.showCancelButtonOnFirstPage = showCancelButtonOnFirstPage;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class AuthenticationUiConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private ApplicationIdConfig targetApplicationId;
      @SuppressFBWarnings(justification = "generated code")
      private List<AuthenticationStepUiConfig> customizedStepUis;
      @SuppressFBWarnings(justification = "generated code")
      private LocationInterpreterConfig languageExtractor;
      @SuppressFBWarnings(justification = "generated code")
      private boolean showGotoButtons;
      @SuppressFBWarnings(justification = "generated code")
      private MaintenanceMessageUiSettings maintenanceMessageUiSettings;
      @SuppressFBWarnings(justification = "generated code")
      private PublicSelfServiceFlowLinkConfig selfUnlockFlow;
      @SuppressFBWarnings(justification = "generated code")
      private TargetURIResolverConfig targetURIResolver;
      @SuppressFBWarnings(justification = "generated code")
      private PublicFlowRedirectTargetConfig cancellationTarget;
      @SuppressFBWarnings(justification = "generated code")
      private FlowRedirectTargetConfig flowFailureTarget;
      @SuppressFBWarnings(justification = "generated code")
      private boolean showCancelButtonOnFirstPage;

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

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfig.AuthenticationUiConfigBuilder targetApplicationId(ApplicationIdConfig targetApplicationId) {
         this.targetApplicationId = targetApplicationId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfig.AuthenticationUiConfigBuilder customizedStepUis(List<AuthenticationStepUiConfig> customizedStepUis) {
         this.customizedStepUis = customizedStepUis;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfig.AuthenticationUiConfigBuilder languageExtractor(LocationInterpreterConfig languageExtractor) {
         this.languageExtractor = languageExtractor;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfig.AuthenticationUiConfigBuilder showGotoButtons(boolean showGotoButtons) {
         this.showGotoButtons = showGotoButtons;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfig.AuthenticationUiConfigBuilder maintenanceMessageUiSettings(MaintenanceMessageUiSettings maintenanceMessageUiSettings) {
         this.maintenanceMessageUiSettings = maintenanceMessageUiSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfig.AuthenticationUiConfigBuilder selfUnlockFlow(PublicSelfServiceFlowLinkConfig selfUnlockFlow) {
         this.selfUnlockFlow = selfUnlockFlow;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfig.AuthenticationUiConfigBuilder targetURIResolver(TargetURIResolverConfig targetURIResolver) {
         this.targetURIResolver = targetURIResolver;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfig.AuthenticationUiConfigBuilder cancellationTarget(PublicFlowRedirectTargetConfig cancellationTarget) {
         this.cancellationTarget = cancellationTarget;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfig.AuthenticationUiConfigBuilder flowFailureTarget(FlowRedirectTargetConfig flowFailureTarget) {
         this.flowFailureTarget = flowFailureTarget;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfig.AuthenticationUiConfigBuilder showCancelButtonOnFirstPage(boolean showCancelButtonOnFirstPage) {
         this.showCancelButtonOnFirstPage = showCancelButtonOnFirstPage;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfig build() {
         return new AuthenticationUiConfig(
            this.targetApplicationId,
            this.customizedStepUis,
            this.languageExtractor,
            this.showGotoButtons,
            this.maintenanceMessageUiSettings,
            this.selfUnlockFlow,
            this.targetURIResolver,
            this.cancellationTarget,
            this.flowFailureTarget,
            this.showCancelButtonOnFirstPage
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "AuthenticationUiConfig.AuthenticationUiConfigBuilder(targetApplicationId="
            + this.targetApplicationId
            + ", customizedStepUis="
            + this.customizedStepUis
            + ", languageExtractor="
            + this.languageExtractor
            + ", showGotoButtons="
            + this.showGotoButtons
            + ", maintenanceMessageUiSettings="
            + this.maintenanceMessageUiSettings
            + ", selfUnlockFlow="
            + this.selfUnlockFlow
            + ", targetURIResolver="
            + this.targetURIResolver
            + ", cancellationTarget="
            + this.cancellationTarget
            + ", flowFailureTarget="
            + this.flowFailureTarget
            + ", showCancelButtonOnFirstPage="
            + this.showCancelButtonOnFirstPage
            + ")";
      }
   }
}
