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

import com.airlock.iam.authentication.application.configuration.targetapp.TargetApplicationsConfig;
import com.airlock.iam.authentication.application.service.ApplicationAccessService;
import com.airlock.iam.authentication.application.service.InternalApplicationAccessService;
import com.airlock.iam.authentication.application.service.flow.session.AuthenticationFlowSessionRepository;
import com.airlock.iam.authentication.application.service.session.ApplicationAccessCompletedServiceImpl;
import com.airlock.iam.authentication.application.service.targetapp.TargetApplicationRepository;
import com.airlock.iam.authentication.domain.model.event.AuthenticationFlowEventFactory;
import com.airlock.iam.authentication.domain.model.session.CurrentApplicationId;
import com.airlock.iam.authentication.domain.model.targetapp.TargetApplication;
import com.airlock.iam.authentication.domain.service.ApplicationAccessCompletedService;
import com.airlock.iam.authentication.domain.service.ApplicationAccessSessionService;
import com.airlock.iam.authentication.domain.service.flow.AuthenticationFlowSessionUserSessionConnector;
import com.airlock.iam.authentication.infrastructure.event.AuthenticationFlowEventFactoryImpl;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.exception.ServiceConfigurationException;
import com.airlock.iam.base.api.infrastructure.plugin.PluginConfigToLogic;
import com.airlock.iam.common.domain.model.user.DefaultPersistedUserInformationAccessHandler;
import com.airlock.iam.common.domain.model.user.PersistedUserInformationAccessHandler;
import com.airlock.iam.flow.api.domain.model.flow.FlowId;
import com.airlock.iam.flow.application.configuration.FlowConfigRepository;
import com.airlock.iam.flow.application.service.session.FlowContinuationChecker;
import com.airlock.iam.flow.domain.model.session.FlowSessionRepository;
import com.airlock.iam.flow.domain.model.session.MultiFlowSession;
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.shared.domain.service.FlowSessionUserSessionConnector;
import com.airlock.iam.flow.types.api.domain.model.session.KnownMultiFlowType;
import com.google.inject.Exposed;
import com.google.inject.PrivateModule;
import com.google.inject.Provides;
import com.google.inject.servlet.RequestScoped;
import java.util.Collections;
import java.util.Optional;
import java.util.stream.Stream;

public final class ApplicationAccessInfrastructureModule extends PrivateModule {
   @Provides
   @Exposed
   @RequestScoped
   TargetApplicationRepository provideTargetApplicationService(
      MultiFlowManager flowManager, Optional<TargetApplicationsConfig> config, PluginConfigToLogic configToLogic
   ) {
      return config.isEmpty()
         ? new TargetApplicationRepository(Collections.emptyList(), Optional.empty(), flowManager)
         : new TargetApplicationRepository(
            Stream.concat(config.get().getApplications().stream(), config.get().getOneShotApplications().stream())
               .map(configToLogic::getLogic)
               .map(TargetApplication.class::cast)
               .toList(),
            Optional.of(configToLogic.getLogic(config.get().getDefaultApplication())),
            flowManager
         );
   }

   @Provides
   @Exposed
   @RequestScoped
   FlowConfigRepository provideFlowConfigRepository(TargetApplicationRepository applicationRepository) {
      return applicationRepository;
   }

   @Provides
   TargetApplicationsConfig provideAuthenticationFlowsConfig(Optional<TargetApplicationsConfig> authenticationFlowsConfig) {
      return ServiceConfigurationException.checkPresent(authenticationFlowsConfig, "An authentication flows configuration must be present");
   }

   @Provides
   TargetApplication provideTargetApplication(TargetApplicationsConfig config, PluginConfigToLogic configToLogic) {
      return configToLogic.getLogic(config.getDefaultApplication());
   }

   @Exposed
   @Provides
   Optional<CurrentApplicationId> provideCurrentApplicationId(Optional<MultiFlowSession> multiFlowSession) {
      return multiFlowSession.filter(session -> session.getType() == KnownMultiFlowType.AUTHENTICATION_AND_AUTHORIZATION)
         .map(MultiFlowSession::getCurrentFlowId)
         .map(id -> new CurrentApplicationId(id.getFlowId()));
   }

   @Provides
   FlowId provideFlowId(Optional<CurrentApplicationId> applicationId) {
      return applicationId.<FlowId>map(appId -> new FlowId(appId.id())).orElseThrow(() -> new FailureException("no current flow ID"));
   }

   protected void configure() {
      this.bind(InternalApplicationAccessService.class).in(RequestScoped.class);
      this.bind(ApplicationAccessService.class).in(RequestScoped.class);
      this.expose(ApplicationAccessService.class);
      this.bind(FlowContinuationChecker.class).to(FlowContinuationCheckerImpl.class);
      this.bind(FlowSessionRepository.class).to(AuthenticationFlowSessionRepository.class).in(RequestScoped.class);
      this.expose(FlowSessionRepository.class);
      this.bind(FlowSessionUserSessionConnector.class).to(AuthenticationFlowSessionUserSessionConnector.class).in(RequestScoped.class);
      this.expose(FlowSessionUserSessionConnector.class);
      this.bind(AuthenticationFlowEventFactory.class).to(AuthenticationFlowEventFactoryImpl.class).in(RequestScoped.class);
      this.bind(FlowCompletionService.class).to(ApplicationAccessSessionService.class).in(RequestScoped.class);
      this.expose(FlowCompletionService.class);
      this.bind(ApplicationAccessCompletedService.class).to(ApplicationAccessCompletedServiceImpl.class).in(RequestScoped.class);
      this.expose(ApplicationAccessCompletedService.class);
      this.bind(PersistedUserInformationAccessHandler.class).to(DefaultPersistedUserInformationAccessHandler.class);
      this.expose(PersistedUserInformationAccessHandler.class);
   }
}
