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

import com.airlock.iam.base.api.infrastructure.plugin.PluginConfigToLogic;
import com.airlock.iam.flow.api.application.configuration.step.FlowStepConfig;
import com.airlock.iam.flow.api.domain.model.flow.Flow;
import com.airlock.iam.flow.api.domain.model.processor.FlowProcessor;
import com.airlock.iam.flow.api.domain.model.session.FlowAttributeRepository;
import com.airlock.iam.flow.api.domain.model.session.FlowSession;
import com.airlock.iam.flow.application.configuration.FlowConfig;
import com.airlock.iam.flow.domain.model.event.GenericStepResultEventFactory;
import com.airlock.iam.flow.domain.model.flow.FlowProvider;
import com.airlock.iam.flow.domain.model.flow.OptionalFlow;
import com.airlock.iam.flow.domain.model.flow.handover.FlowHandover;
import com.airlock.iam.flow.domain.model.handover.DefaultFlowHandover;
import com.airlock.iam.flow.domain.model.session.FlowSessionRepository;
import com.airlock.iam.flow.domain.model.session.MultiFlowSession;
import com.airlock.iam.flow.domain.model.statemachine.FlowState;
import com.airlock.iam.flow.domain.model.statemachine.InternalFlow;
import com.airlock.iam.flow.domain.model.statemachine.runtime.StateMachineFlow;
import com.airlock.iam.flow.domain.model.statemachine.runtime.StepExceptionHandler;
import com.airlock.iam.flow.domain.service.MultiFlowManager;
import com.airlock.iam.flow.domain.service.session.DefaultFlowAttributeRepository;
import com.airlock.iam.flow.shared.domain.model.session.MultiFlowCapableUserSession;
import com.airlock.iam.flow.shared.infrastructure.service.StepExceptionHandlerImpl;
import com.google.inject.Exposed;
import com.google.inject.PrivateModule;
import com.google.inject.Provides;
import com.google.inject.servlet.RequestScoped;
import jakarta.inject.Provider;
import java.util.Collection;
import java.util.Optional;
import java.util.stream.Collectors;

public final class FlowModule extends PrivateModule {
   @Provides
   @Exposed
   Collection<FlowProcessor> provideFlowProcessors(FlowConfig flowConfig, PluginConfigToLogic toLogic) {
      return flowConfig.getProcessors().stream().map(toLogic::getLogic).collect(Collectors.toList());
   }

   @Exposed
   @Provides
   Flow provideFlow(FlowProvider flowProvider) {
      return flowProvider.getCurrentOrStartNew();
   }

   @Exposed
   @Provides
   FlowProvider provideFlowProvider(Provider<StateMachineFlow> flowProvider, FlowSessionRepository flowSessionRepository, MultiFlowManager multiFlowManager) {
      return new FlowProvider(flowSessionRepository, multiFlowManager, flowProvider::get);
   }

   @Exposed
   @Provides
   FlowState currentFlowState(Provider<StateMachineFlow> flowProvider) {
      return ((StateMachineFlow)flowProvider.get()).getCurrentFlowState();
   }

   @Exposed
   @Provides
   Optional<FlowStepConfig> provideCurrentFlowStepConfig(OptionalFlow flow, Provider<StateMachineFlow> flowProvider) {
      return flow.isPresent() ? this.currentFlowState(flowProvider).getStepConfig() : Optional.empty();
   }

   @Exposed
   @Provides
   OptionalFlow provideOptionalFlow(MultiFlowManager multiFlowManager, Provider<StateMachineFlow> flowProvider) {
      return multiFlowManager.getCurrentFlowSession().isPresent() ? OptionalFlow.withFlow((Flow)flowProvider.get()) : OptionalFlow.withoutFlow();
   }

   @Provides
   @Exposed
   Optional<MultiFlowSession> provideOptionalMultiFlowSession(MultiFlowCapableUserSession userSession) {
      return userSession.getMultiFlowSession();
   }

   @Provides
   @Exposed
   FlowAttributeRepository provideFlowAttributeRepository(MultiFlowCapableUserSession userSession) {
      return userSession.getCurrentFlowSession().map(FlowSession::flowAttributes).orElseGet(DefaultFlowAttributeRepository::new);
   }

   protected void configure() {
      this.bind(StepExceptionHandler.class).toInstance(new StepExceptionHandlerImpl());
      this.bind(StateMachineFlow.class);
      this.bind(FlowHandover.class).to(DefaultFlowHandover.class).in(RequestScoped.class);
      this.expose(FlowHandover.class);
      this.bind(InternalFlow.class).to(StateMachineFlow.class);
      this.expose(InternalFlow.class);
      this.bind(GenericStepResultEventFactory.class).to(GenericStepResultEventFactoryImpl.class);
      this.expose(GenericStepResultEventFactory.class);
   }
}
