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

import com.airlock.iam.authentication.infrastructure.service.ui.AuthenticationUiRepository;
import com.airlock.iam.flow.api.application.configuration.step.FlowStepConfig;
import com.airlock.iam.flow.application.configuration.validation.FlowStepPluginConfiguration;
import com.airlock.iam.flow.shared.application.configuration.step.CommonDataEditStepConfig;
import com.airlock.iam.flow.ui.application.configuration.FlowStepUiConfigValidator;
import com.airlock.iam.flow.ui.application.configuration.StepUiPluginConfiguration;
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 java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class AuthenticationUiConfigValidator extends FlowStepUiConfigValidator {
   private final PluginConfiguration authUiConfig;
   private final PluginConfiguration targetAppConfig;

   public AuthenticationUiConfigValidator(
      PluginConfiguration authUiConfig, PluginConfiguration targetAppConfig, ConfigurationEnvironment environment, ConfigurationContext context
   ) {
      super(environment, context);
      this.authUiConfig = authUiConfig;
      this.targetAppConfig = targetAppConfig;
   }

   public void validate() throws ConfigurationException {
      List<FlowStepPluginConfiguration> stepsInFlow = this.getAllTargetApplicationSteps();
      List<StepUiPluginConfiguration> stepUiConfigs = this.getAllStepUiConfigs();
      if (stepUiConfigs.isEmpty()) {
         this.verifyOnlyProductSteps(stepsInFlow);
      } else {
         this.verifyUsingFlowStepsFromReferencedFlow(stepUiConfigs, stepsInFlow);
         this.verifyReferencedStepsHaveValidType(stepUiConfigs, stepsInFlow);
      }

      this.verifyAllDataStepsWithStepId(stepsInFlow);
      this.verifyInteractiveStepsWithGotoTargetsHaveStepIdWhenShowGotoButtons(this.authUiConfig, stepsInFlow);
   }

   private void verifyAllDataStepsWithStepId(List<FlowStepPluginConfiguration> stepConfigs) throws ConfigurationException {
      for (FlowStepPluginConfiguration stepConfig : stepConfigs) {
         if (CommonDataEditStepConfig.class.isAssignableFrom(stepConfig.getConfigType())) {
            Optional<String> stepIdConfig = stepConfig.getStepId(this.getEnv(), this.getContext());
            if (stepIdConfig.isEmpty()) {
               throw new PropertyConstraintViolationException(
                  "targetApplicationId",
                  this.authUiConfig,
                  this.getEnv(),
                  this.getContext(),
                  "At least one " + stepConfig.getPluginDisplayName().orElse("step") + " does not have a step ID configured."
               );
            }
         }
      }
   }

   protected List<StepUiPluginConfiguration> getAllStepUiConfigs() throws PropertyTypeMismatchException {
      return PluginListPropertyValueReader.effectivePluginListPropertyValue(this.authUiConfig, "customizedStepUis", this.getEnv(), this.getContext())
         .stream()
         .map(plugin -> StepUiPluginConfiguration.wrapStepUiPluginConfiguration(plugin, customStepUiConfigTypes()))
         .collect(Collectors.toList());
   }

   public static List<Class<?>> customStepUiConfigTypes() {
      return List.of(ConfigurableAuthenticationStepUiConfig.class, CustomJavaScriptAuthenticationStepUiConfig.class);
   }

   private List<FlowStepPluginConfiguration> getAllTargetApplicationSteps() throws PropertyTypeMismatchException {
      List<FlowStepPluginConfiguration> authenticationSteps = this.getAuthenticationSteps(this.targetAppConfig);
      List<FlowStepPluginConfiguration> authorizationSteps = this.getAuthorizationSteps(this.targetAppConfig);
      return Stream.concat(authenticationSteps.stream(), authorizationSteps.stream()).collect(Collectors.toList());
   }

   private List<FlowStepPluginConfiguration> getAuthenticationSteps(PluginConfiguration targetApplication) throws PropertyTypeMismatchException {
      PluginConfiguration authenticationFlow = PluginPropertyValueReader.effectivePluginPropertyValue(
         targetApplication, "authenticationFlow", this.getEnv(), this.getContext()
      );
      return authenticationFlow == null ? Collections.emptyList() : this.findAllFlowSteps(authenticationFlow);
   }

   private List<FlowStepPluginConfiguration> getAuthorizationSteps(PluginConfiguration targetApplication) throws PropertyTypeMismatchException {
      PluginConfiguration authorizationFlow = PluginPropertyValueReader.effectivePluginPropertyValue(
         targetApplication, "authorizationFlow", this.getEnv(), this.getContext()
      );
      return authorizationFlow == null ? Collections.emptyList() : this.findAllFlowSteps(authorizationFlow);
   }

   protected PropertyConstraintViolationException newStepUiConfigException(String message) {
      return new PropertyConstraintViolationException("customizedStepUis", this.authUiConfig, this.getEnv(), this.getContext(), message);
   }

   protected Set<Class<? extends FlowStepConfig>> defaultUiCapableStepConfigTypes() {
      return AuthenticationUiRepository.DEFAULT_UI_CAPABLE_AUTH_STEP_CONFIG_CLASSES;
   }
}
