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

import com.airlock.iam.authentication.application.configuration.airlock2fa.Airlock2FAUserFactorAuthenticationStepConfig;
import com.airlock.iam.authentication.domain.model.airlock2fa.Airlock2FAAuthenticationStepSession;
import com.airlock.iam.authentication.domain.model.airlock2fa.Airlock2FAAuthenticationStepSettings;
import com.airlock.iam.base.api.infrastructure.plugin.ModuleForConfig;
import com.airlock.iam.base.api.infrastructure.plugin.PluginConfigToLogic;
import com.airlock.iam.common.api.domain.model.valueprovider.ValueProvider;
import com.airlock.iam.factor.application.configuration.airlock2fa.Airlock2FASettings;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FADeviceSettings;
import com.airlock.iam.factor.domain.model.airlock2fa.AuthenticationStatusSettings;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAApprovalAuthTokenIdService;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAAuthenticationService;
import com.airlock.iam.factor.domain.service.airlock2fa.AuthenticationStatusService;
import com.airlock.iam.factor.domain.service.airlock2fa.QrCodeImageReader;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.Airlock2FAAuthenticationServiceImpl;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeAuthenticationRequestFactory;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.QrCodeImageReaderImpl;
import com.airlock.iam.flow.api.application.configuration.tag.TagConfig;
import com.airlock.iam.flow.api.domain.model.session.FlowSession;
import com.airlock.iam.flow.api.domain.model.tag.Tag;
import com.airlock.iam.flow.infrastructure.step.StepSessionModuleHelper;
import com.airlock.iam.flow.shared.api.application.configuration.message.Airlock2FAMessageProviderConfig;
import com.airlock.iam.flow.shared.api.domain.model.message.Airlock2FAMessageProvider;
import com.airlock.iam.flow.shared.infrastructure.factory.Airlock2FAModuleForConfigBase;
import com.google.inject.Exposed;
import com.google.inject.Provides;
import jakarta.annotation.Nullable;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

@ModuleForConfig(Airlock2FAUserFactorAuthenticationStepConfig.class)
public final class Airlock2FAUserFactorAuthenticationStepModule extends Airlock2FAModuleForConfigBase {
   @Provides
   @Exposed
   Airlock2FAAuthenticationStepSettings provideAirlock2FAAuthenticationStepSettings(
      Airlock2FASettings settings,
      Airlock2FAUserFactorAuthenticationStepConfig stepConfig,
      PluginConfigToLogic configToLogic,
      Optional<ValueProvider<String>> loginIdProvider
   ) {
      return Airlock2FAAuthenticationStepSettings.builder()
         .maxFailedPasscodeCheckAttempts(stepConfig.getMaxFailedPasscodeCheckAttempts())
         .deviceSettings(
            new Airlock2FADeviceSettings(stepConfig.getFactorPriority(), stepConfig.getCooldownSettings(), settings.getPayloadEncryptionSettings())
         )
         .enforceDeviceSelection(stepConfig.isEnforceDeviceSelection())
         .pushToAllEnabled(stepConfig.isEnablePushToAll())
         .frontEndBaseUrl(settings.getFuturaeServer().getFrontEndUrl())
         .tagsOnSuccessfulOneTouch(mapTags(stepConfig.getTagsOnSuccessfulOneTouch(), configToLogic))
         .tagsOnSuccessfulOnlineQrCode(mapTags(stepConfig.getTagsOnSuccessfulOnlineQrCode(), configToLogic))
         .tagsOnSuccessfulPasscodeCheck(mapTags(stepConfig.getTagsOnSuccessfulPasscodeCheck(), configToLogic))
         .tagsOnSuccessfulOfflineQrCode(mapTags(stepConfig.getTagsOnSuccessfulOfflineQrCode(), configToLogic))
         .tagsOnSuccessfulBypass(mapTags(stepConfig.getTagsOnSuccessfulBypass(), configToLogic))
         .enableBypassMode(settings.isAllowFuturaeBypassMode())
         .loginIdProvider(loginIdProvider)
         .oneTouchMessageProvider(this.mapMessageProvider(stepConfig.getMessageProvider(), configToLogic))
         .qrCodeMessageProvider(this.mapMessageProvider(stepConfig.getQrCodeMessageProvider(), configToLogic))
         .shortLivedOnlineQrCodeSettings(stepConfig.getShortLivedOnlineQrCodeSettings())
         .build();
   }

   @Provides
   Optional<ValueProvider<String>> provideLoginIdGenerator(Airlock2FAUserFactorAuthenticationStepConfig stepConfig) {
      return stepConfig.isGenerateLoginId() ? Optional.of(() -> stepConfig.getLoginIdGenerator().createString(null)) : Optional.empty();
   }

   private Optional<Airlock2FAMessageProvider> mapMessageProvider(@Nullable Airlock2FAMessageProviderConfig messageProvider, PluginConfigToLogic configToLogic) {
      return Optional.ofNullable(messageProvider).map(configToLogic::getLogic);
   }

   private static Set<Tag> mapTags(List<TagConfig> tagConfigs, PluginConfigToLogic configToLogic) {
      return tagConfigs.stream().map(tagConfig -> configToLogic.getLogic(tagConfig)).collect(Collectors.toSet());
   }

   @Exposed
   @Provides
   Optional<Airlock2FAAuthenticationStepSession> provideAirlock2FAAuthenticationStepSession(FlowSession flowSession) {
      return StepSessionModuleHelper.tryRetrieveStepSession(flowSession, Airlock2FAAuthenticationStepSession.class);
   }

   @Exposed
   @Provides
   Airlock2FASettings provideAirlock2FASettings(Airlock2FAUserFactorAuthenticationStepConfig config) {
      return config.getAirlock2faSettings();
   }

   @Provides
   AuthenticationStatusSettings provideAuthenticationStatusSettings(Airlock2FASettings airlock2FASettings) {
      return new AuthenticationStatusSettings(airlock2FASettings.isLockUserOnFraud());
   }

   @Exposed
   @Provides
   Optional<Airlock2FAApprovalAuthTokenIdService> provideAirlock2FAApprovalAuthTokenIdService() {
      return Optional.empty();
   }

   @Override
   protected void configure() {
      super.configure();
      this.bind(FuturaeAuthenticationRequestFactory.class);
      this.bind(Airlock2FAAuthenticationService.class).to(Airlock2FAAuthenticationServiceImpl.class);
      this.expose(Airlock2FAAuthenticationService.class);
      this.bind(QrCodeImageReader.class).to(QrCodeImageReaderImpl.class);
      this.bind(AuthenticationStatusService.class);
      this.expose(AuthenticationStatusService.class);
   }
}
