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

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.login.rest.application.configuration.LoginappFlowConfigValidationUtils;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.ConfigurationException;
import com.airlock.iam.plugin.api.exception.PropertyConstraintViolationException;
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.framework.misc.config.validation.PluginValidationUtils;
import com.airlock.iam.saml2.application.configuration.sp.Saml2UserIdentifyingStepConfig;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.List;
import java.util.Optional;
import lombok.NonNull;

public final class Saml2SpDefaultAppValidator {
   @NonNull
   private final Optional<PluginConfiguration> authenticationFlowsConfig;
   @NonNull
   private final Optional<PluginConfiguration> saml2Config;
   @NonNull
   private final ConfigurationEnvironment env;
   @NonNull
   private final ConfigurationContext context;

   public Saml2SpDefaultAppValidator(
      @NonNull Optional<PluginConfiguration> authenticationFlowsConfig,
      @NonNull Optional<PluginConfiguration> saml2Config,
      @NonNull ConfigurationEnvironment environment,
      @NonNull ConfigurationContext context
   ) {
      if (authenticationFlowsConfig == null) {
         throw new NullPointerException("authenticationFlowsConfig is marked non-null but is null");
      }

      if (saml2Config == null) {
         throw new NullPointerException("saml2Config is marked non-null but is null");
      }

      if (environment == null) {
         throw new NullPointerException("environment is marked non-null but is null");
      }

      if (context == null) {
         throw new NullPointerException("context is marked non-null but is null");
      }

      this.authenticationFlowsConfig = authenticationFlowsConfig;
      this.saml2Config = saml2Config;
      this.env = environment;
      this.context = context;
   }

   public void validate() throws ConfigurationException {
      for (PluginConfiguration saml2FlowSpConfig : this.allSaml2FlowSpConfigs()) {
         if (this.authenticationFlowsConfig.isEmpty()) {
            throw new PropertyConstraintViolationException(
               "samlFlowSpSettings", this.saml2Config.orElseThrow(), this.env, this.context, "There are no Authentication Flows configured."
            );
         }

         PluginConfiguration applicationIdConfig = this.applicationIdConfigFromApplicationToValidate(saml2FlowSpConfig);
         String applicationId = LoginappFlowConfigValidationUtils.applicationIdFromApplicationIdConfig(applicationIdConfig, this.env, this.context);
         LoginappFlowConfigValidationUtils.ApplicationConfigs applicationConfigs = LoginappFlowConfigValidationUtils.ApplicationConfigs.applicationConfigs(
            this.authenticationFlowsConfig.get(), this.env, this.context
         );
         PluginConfiguration applicationConfig = LoginappFlowConfigValidationUtils.applicationConfigByApplicationId(
               applicationConfigs.allApplications(), applicationId, this.env, this.context
            )
            .orElseThrow(
               () -> new PropertyConstraintViolationException(
                  "applicationId", applicationIdConfig, this.env, this.context, "There is no Target Application configured with the given ID."
               )
            );
         Optional<PluginConfiguration> authenticationFlowConfig = this.authenticationFlowWithoutSaml2UserIdentifyingStep(applicationConfig);
         if (authenticationFlowConfig.isPresent()) {
            throw new PropertyConstraintViolationException(
               "steps",
               authenticationFlowConfig.get(),
               this.env,
               this.context,
               "The Authentication Flow with the ID "
                  + LogUtil.quote(applicationId)
                  + " must have a SAML 2.0 User Identifying Step, because it is used in a SAML 2.0 Flow SP as Default Flow Application ID."
            );
         }
      }
   }

   private PluginConfiguration applicationIdConfigFromApplicationToValidate(PluginConfiguration saml2FlowSpConfig) throws PropertyTypeMismatchException {
      Optional<PluginConfiguration> defaultApplicationIdConfig = Optional.ofNullable(
         PluginPropertyValueReader.effectivePluginPropertyValue(saml2FlowSpConfig, "defaultFlowApplicationId", this.env, this.context)
      );
      if (defaultApplicationIdConfig.isPresent()) {
         return defaultApplicationIdConfig.get();
      }

      LoginappFlowConfigValidationUtils.ApplicationConfigs applicationConfigs = LoginappFlowConfigValidationUtils.ApplicationConfigs.applicationConfigs(
         this.authenticationFlowsConfig.orElseThrow(), this.env, this.context
      );
      PluginConfiguration applicationConfig = applicationConfigs.getDefaultApplication().orElseThrow();
      return LoginappFlowConfigValidationUtils.applicationIdConfigFromApplicationConfig(applicationConfig, this.env, this.context);
   }

   private Optional<PluginConfiguration> authenticationFlowWithoutSaml2UserIdentifyingStep(PluginConfiguration applicationConfig) throws ConfigurationException {
      PluginConfiguration authenticationFlowPlugin = PluginPropertyValueReader.effectivePluginPropertyValue(
         applicationConfig, "authenticationFlow", this.env, this.context
      );
      Optional<PluginConfiguration> saml2UserIdentifyingStep = PluginValidationUtils.collectPluginConfigurationsFromSubTree(
            authenticationFlowPlugin, this.env, this.context, PluginValidationUtils.pluginsOfClass(Saml2UserIdentifyingStepConfig.class)
         )
         .stream()
         .findFirst();
      return saml2UserIdentifyingStep.isEmpty() ? Optional.of(authenticationFlowPlugin) : Optional.empty();
   }

   private List<PluginConfiguration> allSaml2FlowSpConfigs() throws PropertyTypeMismatchException {
      return this.saml2Config.isEmpty()
         ? List.of()
         : PluginListPropertyValueReader.effectivePluginListPropertyValue(this.saml2Config.get(), "samlFlowSpSettings", this.env, this.context);
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class Saml2SpDefaultAppValidatorBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Optional<PluginConfiguration> authenticationFlowsConfig;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<PluginConfiguration> saml2Config;
      @SuppressFBWarnings(justification = "generated code")
      private ConfigurationEnvironment environment;
      @SuppressFBWarnings(justification = "generated code")
      private ConfigurationContext context;

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

      @SuppressFBWarnings(justification = "generated code")
      public Saml2SpDefaultAppValidator.Saml2SpDefaultAppValidatorBuilder authenticationFlowsConfig(
         @NonNull Optional<PluginConfiguration> authenticationFlowsConfig
      ) {
         if (authenticationFlowsConfig == null) {
            throw new NullPointerException("authenticationFlowsConfig is marked non-null but is null");
         }

         this.authenticationFlowsConfig = authenticationFlowsConfig;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2SpDefaultAppValidator.Saml2SpDefaultAppValidatorBuilder saml2Config(@NonNull Optional<PluginConfiguration> saml2Config) {
         if (saml2Config == null) {
            throw new NullPointerException("saml2Config is marked non-null but is null");
         }

         this.saml2Config = saml2Config;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2SpDefaultAppValidator.Saml2SpDefaultAppValidatorBuilder environment(@NonNull ConfigurationEnvironment environment) {
         if (environment == null) {
            throw new NullPointerException("environment is marked non-null but is null");
         }

         this.environment = environment;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2SpDefaultAppValidator.Saml2SpDefaultAppValidatorBuilder context(@NonNull ConfigurationContext context) {
         if (context == null) {
            throw new NullPointerException("context is marked non-null but is null");
         }

         this.context = context;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2SpDefaultAppValidator build() {
         return new Saml2SpDefaultAppValidator(this.authenticationFlowsConfig, this.saml2Config, this.environment, this.context);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Saml2SpDefaultAppValidator.Saml2SpDefaultAppValidatorBuilder(authenticationFlowsConfig="
            + this.authenticationFlowsConfig
            + ", saml2Config="
            + this.saml2Config
            + ", environment="
            + this.environment
            + ", context="
            + this.context
            + ")";
      }
   }
}
