package com.airlock.iam.publicselfservice.infrastructure.factory.shared;

import com.airlock.iam.base.api.domain.model.event.source.EventSource;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.common.domain.model.principal.MaxFailedAttempts;
import com.airlock.iam.common.domain.model.principal.MaxFailedAttemptsImpl;
import com.airlock.iam.flow.api.application.configuration.step.FlowStepConfig;
import com.airlock.iam.flow.application.configuration.FlowConfig;
import com.airlock.iam.flow.application.configuration.FlowConfigRepository;
import com.airlock.iam.flow.application.configuration.flow.FlowConfigHash;
import com.airlock.iam.flow.application.service.flow.GenericFlowConfigRepository;
import com.airlock.iam.flow.application.service.session.FlowContinuationChecker;
import com.airlock.iam.flow.application.service.session.GenericFlowSessionRepository;
import com.airlock.iam.flow.domain.model.session.FlowSessionRepository;
import com.airlock.iam.flow.domain.model.statemachine.FlowState;
import com.airlock.iam.flow.domain.service.EmptyFlowCompletionService;
import com.airlock.iam.flow.domain.service.FlowCompletionService;
import com.airlock.iam.flow.domain.service.MultiFlowManager;
import com.airlock.iam.flow.infrastructure.FlowContinuationCheckerImpl;
import com.airlock.iam.flow.infrastructure.flow.event.FlowEventSourceImpl;
import com.airlock.iam.flow.shared.domain.model.session.PublicFlowsSessionUserSessionConnector;
import com.airlock.iam.flow.shared.domain.model.session.PublicFlowsSessionUserSessionConnectorSettings;
import com.airlock.iam.flow.shared.domain.service.FlowSessionUserSessionConnector;
import com.airlock.iam.flow.types.api.domain.model.session.KnownMultiFlowType;
import com.airlock.iam.flow.types.api.domain.model.session.MultiFlowType;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.publicselfservice.application.configuration.PublicSelfServiceFlowsConfig;
import com.airlock.iam.publicselfservice.application.configuration.flow.PasswordResetLegacyFlow;
import com.airlock.iam.publicselfservice.application.configuration.flow.PublicSelfServiceFlowConfig;
import com.google.inject.Exposed;
import com.google.inject.PrivateModule;
import com.google.inject.Provides;
import com.google.inject.servlet.RequestScoped;
import java.util.Optional;

public final class PublicSelfServiceFlowModule extends PrivateModule {
   @Provides
   @Exposed
   @RequestScoped
   GenericFlowConfigRepository provideFlowConfigRepository(
      MultiFlowManager multiFlowManager, Optional<PublicSelfServiceFlowsConfig> config, Optional<PublicSelfServiceFlowConfig> defaultFlowConfig
   ) {
      return GenericFlowConfigRepository.builder()
         .multiFlowManager(multiFlowManager)
         .flowConfigs(config.stream().flatMap(c -> c.getFlows().stream()).map(FlowConfig.class::cast).toList())
         .defaultFlowId(defaultFlowConfig.map(PublicSelfServiceFlowConfig::getFlowId))
         .build();
   }

   @Provides
   @Exposed
   @RequestScoped
   PublicSelfServiceFlowConfig provideCurrentPublicSelfServiceFlowConfig(GenericFlowConfigRepository flowConfigRepository) {
      return (PublicSelfServiceFlowConfig)flowConfigRepository.getConfigForCurrentFlow()
         .orElseThrow(() -> new FailureException("no current public self-service flow"));
   }

   @Provides
   Optional<PublicSelfServiceFlowConfig> provideDefaultFlowConfigForPasswordResetBackwardsCompatibility(Optional<PublicSelfServiceFlowsConfig> config) {
      return config.stream()
         .flatMap(c -> c.getFlows().stream())
         .filter(flowConfig -> flowConfig.getFlowId().equals(PasswordResetLegacyFlow.LEGACY_PASSWORD_RESET_FLOW_ID))
         .findFirst();
   }

   @Provides
   Optional<FlowConfigHash> provideDefaultFlowConfigHash(Optional<PublicSelfServiceFlowConfig> defaultFlowConfig) {
      return defaultFlowConfig.map(FlowConfig::getFlowConfigHash);
   }

   @Provides
   int provideMaxFailedAttempts(PublicSelfServiceFlowsConfig config) {
      return config.getMaxFailedAttempts();
   }

   @Exposed
   @Provides
   PublicFlowsSessionUserSessionConnectorSettings providePublicFlowsSessionUserSessionConnectorSettings(PublicSelfServiceFlowConfig publicSelfServiceFlowConfig) {
      return new PublicFlowsSessionUserSessionConnectorSettings(publicSelfServiceFlowConfig.isInitializeNextAuthFlow());
   }

   @Exposed
   @Provides
   EventSource provideFlowEventSource(ConfigurationContext context, FlowConfig config, FlowState flowState) {
      return FlowEventSourceImpl.flowEventSource(
         context, config.getFlowId(), KnownMultiFlowType.PUBLIC_SELF_SERVICE, flowState.getStepConfig().flatMap(FlowStepConfig::getStepId)
      );
   }

   protected void configure() {
      this.bind(FlowSessionUserSessionConnector.class).to(PublicFlowsSessionUserSessionConnector.class).in(RequestScoped.class);
      this.expose(FlowSessionUserSessionConnector.class);
      this.bind(FlowConfigRepository.class).to(GenericFlowConfigRepository.class).in(RequestScoped.class);
      this.expose(FlowConfigRepository.class);
      this.bind(FlowContinuationChecker.class).to(FlowContinuationCheckerImpl.class);
      this.bind(FlowSessionRepository.class).to(GenericFlowSessionRepository.class).in(RequestScoped.class);
      this.expose(FlowSessionRepository.class);
      this.bind(GenericFlowSessionRepository.class).in(RequestScoped.class);
      this.expose(GenericFlowSessionRepository.class);
      this.bind(FlowCompletionService.class).to(EmptyFlowCompletionService.class);
      this.expose(FlowCompletionService.class);
      this.bind(MultiFlowType.class).toInstance(KnownMultiFlowType.PUBLIC_SELF_SERVICE);
      this.expose(MultiFlowType.class);
      this.bind(MaxFailedAttempts.class).to(MaxFailedAttemptsImpl.class);
      this.expose(MaxFailedAttempts.class);
   }
}
