package com.airlock.iam.transactionapproval.infrastructure.factory;

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.api.domain.model.store.user.UserStore;
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.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.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.login.domain.model.existingsessionbehavior.DisabledExistingUserSessionBehaviorLogic;
import com.airlock.iam.login.domain.model.existingsessionbehavior.ExistingUserSessionBehaviorLogic;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.transactionapproval.application.configuration.TransactionApprovalApp;
import com.airlock.iam.transactionapproval.application.configuration.flow.TransactionApprovalFlowConfig;
import com.airlock.iam.transactionapproval.domain.service.TransactionApprovalCompletedService;
import com.airlock.iam.transactionapproval.domain.service.TransactionApprovalFlowSessionUserSessionConnector;
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 TransactionApprovalFlowModule extends PrivateModule {
   @Provides
   @Exposed
   @RequestScoped
   GenericFlowConfigRepository provideFlowConfigRepository(TransactionApprovalApp config, MultiFlowManager flowManager) {
      return GenericFlowConfigRepository.builder()
         .multiFlowManager(flowManager)
         .defaultFlowId(config.getDefaultFlow().map(FlowConfig::getFlowId))
         .flowConfigs(config.getFlows().stream().map(FlowConfig.class::cast).toList())
         .build();
   }

   @Provides
   @RequestScoped
   Optional<FlowConfigHash> provideDefaultFlowConfigHash(TransactionApprovalApp config) {
      return config.getDefaultFlow().map(FlowConfig::getFlowConfigHash);
   }

   @Provides
   TransactionApprovalFlowConfig provideCurrentFlowConfig(GenericFlowConfigRepository flowConfigRepository) {
      return (TransactionApprovalFlowConfig)flowConfigRepository.getConfigForCurrentFlow().orElseThrow(() -> new FailureException("No current flow found"));
   }

   @Provides
   int provideMaxFailedAttempts(TransactionApprovalApp config) {
      return config.getMaxFailedTransactionApprovals();
   }

   @Exposed
   @Provides
   ExistingUserSessionBehaviorLogic provideExistingUserSessionBehaviorLogic() {
      return new DisabledExistingUserSessionBehaviorLogic();
   }

   @Exposed
   @Provides
   @RequestScoped
   UserStore provideUserStore(TransactionApprovalApp config) {
      return config.getCachedUserStore();
   }

   @Exposed
   @Provides
   @RequestScoped
   Optional<UserStore> provideOptionalUserStore(TransactionApprovalApp config) {
      return Optional.of(config.getCachedUserStore());
   }

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

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