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

import com.airlock.iam.flow.api.application.configuration.MaintenanceMessageUiSettings;
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.application.configuration.flow.FlowIdConfig;
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.OnCompletedConfigValidator;
import com.airlock.iam.flow.ui.application.configuration.OnCompletedRedirectTargetConfig;
import com.airlock.iam.flow.ui.application.configuration.PublicFlowRedirectTargetConfig;
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 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;
import org.apache.commons.lang3.Strings;

@Plugin(
   description = "User interface for a public self-service flow.<p>Note that the corresponding public self-service flow must also be configured in order for this user interface to be available.</p>",
   displayName = "Public Self-Service 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 class PublicSelfServiceUiConfig implements FlowUiConfig {
   @PluginProperty(
      description = "The identifier of the public self-service flow that the user interface configuration refers to.",
      displayName = "Flow ID",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private FlowIdConfig flowId;
   public static final String FLOW_ID_PROPERTY = "flowId";
   @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<PublicSelfServiceStepUiConfig> customizedStepUis;
   public static final String CUSTOMIZED_STEP_UIS = "customizedStepUis";
   @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>public-self-service.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;
   @PluginProperty(
      description = "The target to redirect to when the public self-service flow is successfully completed. If \"Show Confirmation Page\" is enabled, the page is displayed first and the redirect happens after clicking the \"continue\" button. If the confirmation page is disabled, this property is mandatory.",
      displayName = "Completion Target",
      optional = true
   )
   @GroupProperty(groupIdentifier = "On Flow Completion")
   private OnCompletedRedirectTargetConfig completionTarget;
   @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;
   @BooleanProperty(
      description = "If enabled, a confirmation page is shown after a completed public self-service. Otherwise, the user is directly redirected to the configured 'Completion Target' and a corresponding feedback message '<tt>public-self-service.pages.messages.completed</tt>' is shown.",
      displayName = "Show Confirmation Page",
      optional = true,
      defaultValue = "true"
   )
   @GroupProperty(groupIdentifier = "On Flow Completion")
   private boolean showConfirmationPage;
   private static final String SHOW_CONFIRMATION_PAGE_PROPERTY = "showConfirmationPage";

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

   @ConfigurationValidator
   static void validateOnCompletedConfig(PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment) throws ConfigurationException {
      new OnCompletedConfigValidator(pc, env, context).validate();
   }

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

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

   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 this.flowId.getId();
   }

   public List<PublicSelfServiceStepUiConfig> getCustomizedStepUis() {
      return this.customizedStepUis;
   }

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

   public OnCompletedRedirectTargetConfig getCompletionTarget() {
      return this.completionTarget;
   }

   public FlowRedirectTargetConfig getFlowFailureTarget() {
      return this.flowFailureTarget;
   }

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

   public boolean isShowConfirmationPage() {
      return this.showConfirmationPage;
   }

   private Optional<PublicSelfServiceStepUiConfig> findUiConfigMatchingSpecificStepAction(StepId stepId, StepAction stepAction) {
      return this.getCustomizedStepUis()
         .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<PublicSelfServiceStepUiConfig> findUiConfigMatchingAllStepActions(StepId stepId) {
      return this.getCustomizedStepUis().stream().filter(c -> c.getRequiredStepAction().isEmpty()).filter(c -> stepId.equals(c.getStepId())).findFirst();
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public PublicSelfServiceUiConfig.PublicSelfServiceUiConfigBuilder toBuilder() {
      PublicSelfServiceUiConfig.PublicSelfServiceUiConfigBuilder builder = new PublicSelfServiceUiConfig.PublicSelfServiceUiConfigBuilder()
         .flowId(this.flowId)
         .showGotoButtons(this.showGotoButtons)
         .maintenanceMessageUiSettings(this.maintenanceMessageUiSettings)
         .completionTarget(this.completionTarget)
         .cancellationTarget(this.cancellationTarget)
         .flowFailureTarget(this.flowFailureTarget)
         .showCancelButtonOnFirstPage(this.showCancelButtonOnFirstPage)
         .showConfirmationPage(this.showConfirmationPage);
      if (this.customizedStepUis != null) {
         builder.customizedStepUis(this.customizedStepUis);
      }

      return builder;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public PublicSelfServiceUiConfig(
      FlowIdConfig flowId,
      List<PublicSelfServiceStepUiConfig> customizedStepUis,
      boolean showGotoButtons,
      MaintenanceMessageUiSettings maintenanceMessageUiSettings,
      OnCompletedRedirectTargetConfig completionTarget,
      PublicFlowRedirectTargetConfig cancellationTarget,
      FlowRedirectTargetConfig flowFailureTarget,
      boolean showCancelButtonOnFirstPage,
      boolean showConfirmationPage
   ) {
      this.flowId = flowId;
      this.customizedStepUis = customizedStepUis;
      this.showGotoButtons = showGotoButtons;
      this.maintenanceMessageUiSettings = maintenanceMessageUiSettings;
      this.completionTarget = completionTarget;
      this.cancellationTarget = cancellationTarget;
      this.flowFailureTarget = flowFailureTarget;
      this.showCancelButtonOnFirstPage = showCancelButtonOnFirstPage;
      this.showConfirmationPage = showConfirmationPage;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class PublicSelfServiceUiConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private FlowIdConfig flowId;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<PublicSelfServiceStepUiConfig> customizedStepUis;
      @SuppressFBWarnings(justification = "generated code")
      private boolean showGotoButtons;
      @SuppressFBWarnings(justification = "generated code")
      private MaintenanceMessageUiSettings maintenanceMessageUiSettings;
      @SuppressFBWarnings(justification = "generated code")
      private OnCompletedRedirectTargetConfig completionTarget;
      @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")
      private boolean showConfirmationPage;

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

      @SuppressFBWarnings(justification = "generated code")
      public PublicSelfServiceUiConfig.PublicSelfServiceUiConfigBuilder flowId(FlowIdConfig flowId) {
         this.flowId = flowId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public PublicSelfServiceUiConfig.PublicSelfServiceUiConfigBuilder customizedStepUi(PublicSelfServiceStepUiConfig customizedStepUi) {
         if (this.customizedStepUis == null) {
            this.customizedStepUis = new ArrayList<>();
         }

         this.customizedStepUis.add(customizedStepUi);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public PublicSelfServiceUiConfig.PublicSelfServiceUiConfigBuilder customizedStepUis(Collection<? extends PublicSelfServiceStepUiConfig> customizedStepUis) {
         if (customizedStepUis == null) {
            throw new NullPointerException("customizedStepUis cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public PublicSelfServiceUiConfig.PublicSelfServiceUiConfigBuilder clearCustomizedStepUis() {
         if (this.customizedStepUis != null) {
            this.customizedStepUis.clear();
         }

         return this;
      }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public PublicSelfServiceUiConfig.PublicSelfServiceUiConfigBuilder completionTarget(OnCompletedRedirectTargetConfig completionTarget) {
         this.completionTarget = completionTarget;
         return this;
      }

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      public PublicSelfServiceUiConfig.PublicSelfServiceUiConfigBuilder showConfirmationPage(boolean showConfirmationPage) {
         this.showConfirmationPage = showConfirmationPage;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public PublicSelfServiceUiConfig build() {
         return new PublicSelfServiceUiConfig(
            this.flowId,
            switch (this.customizedStepUis == null ? 0 : this.customizedStepUis.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.customizedStepUis.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.customizedStepUis));
            },
            this.showGotoButtons,
            this.maintenanceMessageUiSettings,
            this.completionTarget,
            this.cancellationTarget,
            this.flowFailureTarget,
            this.showCancelButtonOnFirstPage,
            this.showConfirmationPage
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "PublicSelfServiceUiConfig.PublicSelfServiceUiConfigBuilder(flowId="
            + this.flowId
            + ", customizedStepUis="
            + this.customizedStepUis
            + ", showGotoButtons="
            + this.showGotoButtons
            + ", maintenanceMessageUiSettings="
            + this.maintenanceMessageUiSettings
            + ", completionTarget="
            + this.completionTarget
            + ", cancellationTarget="
            + this.cancellationTarget
            + ", flowFailureTarget="
            + this.flowFailureTarget
            + ", showCancelButtonOnFirstPage="
            + this.showCancelButtonOnFirstPage
            + ", showConfirmationPage="
            + this.showConfirmationPage
            + ")";
      }
   }
}
