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

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.flow.application.configuration.validation.FlowStepPluginConfiguration;
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.api.valuereader.StringPropertyValueReader;
import com.airlock.iam.plugin.framework.application.configuration.PluginConfigurationWrapper;
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 java.util.stream.Collectors;
import lombok.NonNull;

public final class Saml2UserIdentifyingFlowStepConfigValidator {
   @NonNull
   private final Optional<PluginConfiguration> authenticationFlows;
   @NonNull
   private final Optional<PluginConfiguration> samlConfig;
   @NonNull
   private final ConfigurationEnvironment environment;
   @NonNull
   private final ConfigurationContext context;

   public void validate() throws ConfigurationException {
      List<PluginConfiguration> samlFlowSpConfigs = this.samlFlowSpConfigs();

      for (PluginConfiguration samlUserIdentifyingStep : this.getAllSaml2UserIdentifyingSteps()) {
         String samlUserIdentifyingStepSpEntityId = this.getSaml2UserIdentifyingStepSpEntityId(samlUserIdentifyingStep);
         if (!this.saml2FlowSpConfigExists(samlFlowSpConfigs, samlUserIdentifyingStepSpEntityId)) {
            throw new PropertyConstraintViolationException(
               "entityId",
               samlUserIdentifyingStep,
               this.environment,
               this.context,
               "A SAML 2.0 Flow SP with the SP Entity ID "
                  + LogUtil.quote(samlUserIdentifyingStepSpEntityId)
                  + " must be configured in the Loginapp's SAML SP Settings (Flow Auth)"
            );
         }
      }
   }

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

   private List<PluginConfiguration> getAllSaml2UserIdentifyingSteps() {
      return this.authenticationFlows.isEmpty()
         ? List.of()
         : PluginValidationUtils.collectPluginConfigurationsFromSubTree(
               this.authenticationFlows.get(), this.environment, this.context, PluginValidationUtils.pluginsOfClass(Saml2UserIdentifyingStepConfig.class)
            )
            .stream()
            .map(FlowStepPluginConfiguration::wrapFlowStepPluginConfiguration)
            .<PluginConfiguration>map(PluginConfigurationWrapper::getPluginConfiguration)
            .collect(Collectors.toList());
   }

   private String getSaml2UserIdentifyingStepSpEntityId(PluginConfiguration samlUserIdentifyingStep) throws PropertyTypeMismatchException {
      PluginConfiguration spId = PluginPropertyValueReader.effectivePluginPropertyValue(samlUserIdentifyingStep, "entityId", this.environment, this.context);
      return StringPropertyValueReader.effectiveStringConfigValue(spId, "spEntityId", this.environment, this.context);
   }

   private boolean saml2FlowSpConfigExists(List<PluginConfiguration> samlFlowSpConfigs, String spEntityId) throws PropertyTypeMismatchException {
      for (PluginConfiguration samlFlowSpConfig : samlFlowSpConfigs) {
         String saml2FlowSpConfigEntityId = this.getSaml2FlowSpConfigEntityId(samlFlowSpConfig);
         if (spEntityId.equals(saml2FlowSpConfigEntityId)) {
            return true;
         }
      }

      return false;
   }

   private String getSaml2FlowSpConfigEntityId(PluginConfiguration saml2FlowSpConfig) throws PropertyTypeMismatchException {
      PluginConfiguration spEntity = PluginPropertyValueReader.effectivePluginPropertyValue(saml2FlowSpConfig, "spEntityConfig", this.environment, this.context);
      PluginConfiguration spId = PluginPropertyValueReader.effectivePluginPropertyValue(spEntity, "entityId", this.environment, this.context);
      return StringPropertyValueReader.effectiveStringConfigValue(spId, "spEntityId", this.environment, this.context);
   }

   @SuppressFBWarnings(justification = "generated code")
   Saml2UserIdentifyingFlowStepConfigValidator(
      @NonNull Optional<PluginConfiguration> authenticationFlows,
      @NonNull Optional<PluginConfiguration> samlConfig,
      @NonNull ConfigurationEnvironment environment,
      @NonNull ConfigurationContext context
   ) {
      if (authenticationFlows == null) {
         throw new NullPointerException("authenticationFlows is marked non-null but is null");
      }

      if (samlConfig == null) {
         throw new NullPointerException("samlConfig 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.authenticationFlows = authenticationFlows;
      this.samlConfig = samlConfig;
      this.environment = environment;
      this.context = context;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class Saml2UserIdentifyingFlowStepConfigValidatorBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Optional<PluginConfiguration> authenticationFlows;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<PluginConfiguration> samlConfig;
      @SuppressFBWarnings(justification = "generated code")
      private ConfigurationEnvironment environment;
      @SuppressFBWarnings(justification = "generated code")
      private ConfigurationContext context;

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

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

         this.authenticationFlows = authenticationFlows;
         return this;
      }

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

         this.samlConfig = samlConfig;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2UserIdentifyingFlowStepConfigValidator.Saml2UserIdentifyingFlowStepConfigValidatorBuilder 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 Saml2UserIdentifyingFlowStepConfigValidator.Saml2UserIdentifyingFlowStepConfigValidatorBuilder 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 Saml2UserIdentifyingFlowStepConfigValidator build() {
         return new Saml2UserIdentifyingFlowStepConfigValidator(this.authenticationFlows, this.samlConfig, this.environment, this.context);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Saml2UserIdentifyingFlowStepConfigValidator.Saml2UserIdentifyingFlowStepConfigValidatorBuilder(authenticationFlows="
            + this.authenticationFlows
            + ", samlConfig="
            + this.samlConfig
            + ", environment="
            + this.environment
            + ", context="
            + this.context
            + ")";
      }
   }
}
