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

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.flow.api.domain.model.flow.ApplicationId;
import com.airlock.iam.flow.ui.application.configuration.OnLogoutConfig;
import com.airlock.iam.flow.ui.application.configuration.ShowLogoutDisclaimerPageConfig;
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.annotation.property.StringListProperty;
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.exception.PropertyTypeMismatchException;
import com.airlock.iam.plugin.api.valuereader.PluginListPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.PluginPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;

@Plugin(description = "User interface configurations for authentication and authorization.", displayName = "Authentication & Authorization UIs")
public final class AuthenticationUiConfigs {
   @PluginListProperty(
      description = "Allows to configure the user interface for the flow belonging to a target application.",
      displayName = "Flow UIs",
      optional = false,
      minElements = 1
   )
   private List<AuthenticationUiConfig> flowUis;
   public static final String FLOW_UIS_FIELD = "flowUis";
   public static final String FLOW_UIS_DISPLAY_NAME = "Flow UIs";
   @PluginProperty(
      description = "Defines UI settings for pages that are not flow specific.",
      displayName = "Non-Flow UI Settings",
      defaultValue = NonFlowAuthenticationUiConfig.class,
      optional = true
   )
   private NonFlowAuthenticationUiConfig nonFlowUiSettings;
   @PluginProperty(
      description = "The action to take after a logout.",
      defaultValue = ShowLogoutDisclaimerPageConfig.class,
      displayName = "On Logout",
      optional = true
   )
   private OnLogoutConfig onLogout;
   public static final String ON_LOGOUT_FIELD = "onLogout";
   public static final String ON_LOGOUT_FIELD_DISPLAY_NAME = "On Logout";
   @StringListProperty(
      description = "Names of SSO parameters that the SPA tries to extract. The names are used in the configured order and extracting stops with the first parameter that is present.",
      displayName = "SSO Parameter Names",
      optional = true,
      defaultValue = "sso",
      exampleElements = {"sso", "representation"}
   )
   private List<String> ssoParameterNames;

   public NonFlowAuthenticationUiConfig getNonFlowUiSettings() {
      return this.nonFlowUiSettings;
   }

   public List<String> getSsoParameterNames() {
      return this.ssoParameterNames;
   }

   public Optional<AuthenticationUiConfig> byApplicationId(ApplicationId applicationId) {
      return this.flowUis.stream().filter(config -> this.matchesId(config, applicationId)).findFirst();
   }

   public Optional<OnLogoutConfig> getOnLogout() {
      return Optional.ofNullable(this.onLogout);
   }

   private boolean matchesId(AuthenticationUiConfig config, ApplicationId id) {
      return config.getTargetApplicationId().equals(id);
   }

   @ConfigurationValidator
   public static void validateUniqueTargetApplications(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      List<String> applicationIds = applicationIds(config, env, context);
      Set<String> seenIds = new HashSet<>();

      for (String id : applicationIds) {
         boolean unique = seenIds.add(id);
         if (!unique) {
            throw new PropertyConfigurationException(
               config, env, context, "Application ID " + LogUtil.quote(id) + " is not unique in " + LogUtil.quote("Applications UIs"), new String[]{"flowUis"}
            );
         }
      }
   }

   private static List<String> applicationIds(PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context) throws PropertyTypeMismatchException {
      List<String> ids = new LinkedList<>();

      for (PluginConfiguration uiConfig : PluginListPropertyValueReader.effectivePluginListPropertyValue(config, "flowUis", env, context)) {
         ids.add(getApplicationId(uiConfig, env, context));
      }

      return ids;
   }

   private static String getApplicationId(PluginConfiguration uiConfig, ConfigurationEnvironment env, ConfigurationContext context) throws PropertyTypeMismatchException {
      PluginConfiguration idConfig = PluginPropertyValueReader.effectivePluginPropertyValue(uiConfig, "targetApplicationId", env, context);
      return idConfig == null ? null : StringPropertyValueReader.effectiveStringConfigValue(idConfig, "applicationId", env, context);
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public AuthenticationUiConfigs(
      List<AuthenticationUiConfig> flowUis, NonFlowAuthenticationUiConfig nonFlowUiSettings, OnLogoutConfig onLogout, List<String> ssoParameterNames
   ) {
      this.flowUis = flowUis;
      this.nonFlowUiSettings = nonFlowUiSettings;
      this.onLogout = onLogout;
      this.ssoParameterNames = ssoParameterNames;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class AuthenticationUiConfigsBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<AuthenticationUiConfig> flowUis;
      @SuppressFBWarnings(justification = "generated code")
      private NonFlowAuthenticationUiConfig nonFlowUiSettings;
      @SuppressFBWarnings(justification = "generated code")
      private OnLogoutConfig onLogout;
      @SuppressFBWarnings(justification = "generated code")
      private List<String> ssoParameterNames;

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

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfigs.AuthenticationUiConfigsBuilder flowUi(AuthenticationUiConfig flowUi) {
         if (this.flowUis == null) {
            this.flowUis = new ArrayList<>();
         }

         this.flowUis.add(flowUi);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfigs.AuthenticationUiConfigsBuilder flowUis(Collection<? extends AuthenticationUiConfig> flowUis) {
         if (flowUis == null) {
            throw new NullPointerException("flowUis cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfigs.AuthenticationUiConfigsBuilder clearFlowUis() {
         if (this.flowUis != null) {
            this.flowUis.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfigs.AuthenticationUiConfigsBuilder nonFlowUiSettings(NonFlowAuthenticationUiConfig nonFlowUiSettings) {
         this.nonFlowUiSettings = nonFlowUiSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfigs.AuthenticationUiConfigsBuilder onLogout(OnLogoutConfig onLogout) {
         this.onLogout = onLogout;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AuthenticationUiConfigs.AuthenticationUiConfigsBuilder ssoParameterNames(List<String> ssoParameterNames) {
         this.ssoParameterNames = ssoParameterNames;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "AuthenticationUiConfigs.AuthenticationUiConfigsBuilder(flowUis="
            + this.flowUis
            + ", nonFlowUiSettings="
            + this.nonFlowUiSettings
            + ", onLogout="
            + this.onLogout
            + ", ssoParameterNames="
            + this.ssoParameterNames
            + ")";
      }
   }
}
