package com.airlock.iam.admin.infrastructure.factory.prot;

import com.airlock.iam.admin.api.domain.model.administrator.CurrentAdministrator;
import com.airlock.iam.admin.api.domain.service.user.ManagedUserRepository;
import com.airlock.iam.admin.application.configuration.airlock2fa.Airlock2FACreateActivationLettersConfig;
import com.airlock.iam.admin.application.configuration.airlock2fa.Airlock2FAOrderActivationLettersConfig;
import com.airlock.iam.admin.application.configuration.airlock2fa.Airlock2FAShipmentLettersConfig;
import com.airlock.iam.admin.application.configuration.airlock2fa.Airlock2FATokenControllerConfig;
import com.airlock.iam.admin.application.service.token.airlock2fa.Airlock2FAAccessController;
import com.airlock.iam.admin.application.service.token.airlock2fa.Airlock2FAAdminService;
import com.airlock.iam.admin.application.service.token.airlock2fa.Airlock2FAHardwareTokenService;
import com.airlock.iam.admin.application.service.token.airlock2fa.Airlock2FAHardwareTokenSettings;
import com.airlock.iam.admin.domain.model.administrator.permissions.AdminappAction;
import com.airlock.iam.admin.domain.model.authmethod.AuthenticationMethod;
import com.airlock.iam.admin.domain.model.token.TokenController;
import com.airlock.iam.admin.domain.model.token.airlock2fa.Airlock2FAAuthenticationMethod;
import com.airlock.iam.admin.domain.service.access.AdminappAccessController;
import com.airlock.iam.admin.domain.service.token.airlock2fa.Airlock2FAAdminApiDeviceService;
import com.airlock.iam.admin.domain.service.token.airlock2fa.Airlock2FAAdminApiHardwareTokenService;
import com.airlock.iam.admin.domain.service.token.airlock2fa.Airlock2FAShipmentLetterPrintingService;
import com.airlock.iam.admin.domain.service.token.airlock2fa.Airlock2FAUserAccountRepository;
import com.airlock.iam.admin.infrastructure.service.token.airlock2fa.Airlock2FAActivationLetterViewerFactory;
import com.airlock.iam.admin.infrastructure.service.token.airlock2fa.Airlock2FAShipmentLetterPrintingServiceImpl;
import com.airlock.iam.admin.infrastructure.service.token.airlock2fa.Airlock2FAUserAccountRepositoryImpl;
import com.airlock.iam.admin.infrastructure.service.token.airlock2fa.FuturaeAdminApiDeviceServiceImpl;
import com.airlock.iam.admin.infrastructure.service.token.airlock2fa.FuturaeAdminApiHardwareTokenServiceImpl;
import com.airlock.iam.base.api.domain.model.exception.ServiceConfigurationException;
import com.airlock.iam.common.domain.model.jwt.JwtSecret;
import com.airlock.iam.common.domain.service.jwt.JwtHmacAndAesGcmEncryptionImpl;
import com.airlock.iam.common.infrastructure.restclient.RestClient;
import com.airlock.iam.core.domain.service.jwt.JwtHmacAndAesGcmEncryption;
import com.airlock.iam.database.infrastructure.JooqConfig;
import com.airlock.iam.factor.application.configuration.airlock2fa.Airlock2FARepositoryConfig;
import com.airlock.iam.factor.application.configuration.airlock2fa.Airlock2FASettings;
import com.airlock.iam.factor.application.configuration.airlock2fa.FuturaeServerConfig;
import com.airlock.iam.factor.domain.model.airlock2fa.activation.Airlock2FAActivationSettings;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAActivationLetterOrderServiceImpl;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAActivationLetterPrintingService;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAActivationLetterService;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAAdminApiUserAccountService;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAAdminApiUserDeleteService;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAAdminApiUserService;
import com.airlock.iam.factor.domain.service.airlock2fa.QrCodeImageReader;
import com.airlock.iam.factor.domain.service.airlock2fa.activation.Airlock2FAActivationCodeMapper;
import com.airlock.iam.factor.domain.service.airlock2fa.activation.Airlock2FAActivationLetterOrderService;
import com.airlock.iam.factor.domain.service.airlock2fa.activation.Airlock2FAActivationLetterRepository;
import com.airlock.iam.factor.domain.service.airlock2fa.activation.Airlock2FAFindActivationLetterOrderService;
import com.airlock.iam.factor.domain.service.airlock2fa.displayname.Airlock2FAAccountDisplayNameProvider;
import com.airlock.iam.factor.domain.service.airlock2fa.enrollment.Airlock2FAAdminApiEnrollmentService;
import com.airlock.iam.factor.domain.service.airlock2fa.repository.Airlock2FAActivationLetterOrderRepository;
import com.airlock.iam.factor.domain.service.airlock2fa.repository.Airlock2FAUserRepository;
import com.airlock.iam.factor.infrastructure.repository.airlock2fa.Airlock2FAActivationCodeMapperImpl;
import com.airlock.iam.factor.infrastructure.repository.airlock2fa.Airlock2FAActivationLetterOrderRepositoryImpl;
import com.airlock.iam.factor.infrastructure.repository.airlock2fa.Airlock2FAActivationLetterRepositoryImpl;
import com.airlock.iam.factor.infrastructure.repository.airlock2fa.Airlock2FAUserRepositoryImpl;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeAdminApiApiUserAccountServiceImpl;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeAdminApiEnrollmentRequestFactory;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeAdminApiEnrollmentServiceImpl;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeAdminApiRequestFactory;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeAdminApiRequestFactoryImpl;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeAdminApiUserDeleteServiceImpl;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.FuturaeRequestFactory;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.QrCodeImageReaderImpl;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.header.FuturaeAuthenticationData;
import com.airlock.iam.factor.infrastructure.rest.airlock2fa.futurae.header.FuturaeAuthenticationDataFactory;
import com.airlock.iam.factor.infrastructure.service.airlock2fa.Airlock2FAActivationLetterPrintingServiceImpl;
import com.airlock.iam.factor.infrastructure.service.airlock2fa.Airlock2FAActivationLetterServiceImpl;
import com.airlock.iam.factor.infrastructure.service.airlock2fa.FuturaeAdminApiUserServiceImpl;
import com.google.inject.Exposed;
import com.google.inject.PrivateModule;
import com.google.inject.Provides;
import com.google.inject.servlet.RequestScoped;
import jakarta.inject.Named;
import jakarta.inject.Provider;
import java.io.File;
import java.time.Duration;
import java.util.Optional;

public final class Airlock2FAModule extends PrivateModule {
   @Provides
   Airlock2FASettings provideAirlock2FASettings(Airlock2FATokenControllerConfig config) {
      return config.getAirlock2faSettings();
   }

   @Provides
   Airlock2FARepositoryConfig provideAirlock2FARepositoryConfig(Airlock2FASettings settings) {
      return settings.getRepository();
   }

   @Provides
   FuturaeServerConfig provideFuturaeServerSettings(Airlock2FASettings settings) {
      return settings.getFuturaeServer();
   }

   @Provides
   Airlock2FAAccountDisplayNameProvider provideUserEnrollmentDisplayNameProvider(Airlock2FASettings settings) {
      return settings.getAccountDisplayNameProvider().logic();
   }

   @Provides
   JooqConfig provideJooqConfig(Airlock2FARepositoryConfig settings) {
      return settings.jooqConfig();
   }

   @Provides
   RestClient provideRestClient(FuturaeServerConfig serverConfig) {
      return serverConfig.getRestClient();
   }

   @Provides
   @RequestScoped
   @Exposed
   @Named("AIRLOCK_2FA")
   AuthenticationMethod provideAuthenticationMethod(
      Airlock2FATokenControllerConfig config,
      Airlock2FAAdminApiUserAccountService userCreateService,
      Optional<Airlock2FAActivationLetterOrderService> letterOrderService,
      CurrentAdministrator currentAdministrator,
      Airlock2FAAccountDisplayNameProvider displayNameProvider,
      ManagedUserRepository userRepository
   ) {
      return Airlock2FAAuthenticationMethod.builder()
         .configuredId(config.getConfiguredId())
         .userCreateService(userCreateService)
         .letterOrderService(letterOrderService)
         .autoOrder(config.getOrderActivationLetters().map(Airlock2FAOrderActivationLettersConfig::isAutoOrder).orElse(false))
         .administrator(currentAdministrator)
         .displayNameProvider(displayNameProvider)
         .userRepository(userRepository)
         .build();
   }

   @Provides
   Airlock2FATokenControllerConfig provideAirlock2FATokenControllerConfig(Provider<TokenController.TokenControllers> tokenControllers) {
      Optional<Airlock2FATokenControllerConfig> tokenController = ((TokenController.TokenControllers)tokenControllers.get()).ofId("AIRLOCK_2FA");
      return (Airlock2FATokenControllerConfig)ServiceConfigurationException.checkPresent(tokenController, "Airlock 2FA token controller must be configured.");
   }

   @Provides
   Optional<Airlock2FAShipmentLettersConfig> provideAirlock2FAShipmentLettersConfig(Airlock2FATokenControllerConfig config) {
      return config.getCreateShipmentLetters();
   }

   @Provides
   FuturaeAuthenticationData provideFuturaeAuthenticationData(FuturaeServerConfig serverConfig) {
      return FuturaeAuthenticationDataFactory.createFuturaeAdminAuthenticationData(serverConfig);
   }

   @Provides
   Airlock2FAHardwareTokenSettings provideAirlock2FAHardwareTokenSettings(Airlock2FATokenControllerConfig config) {
      return new Airlock2FAHardwareTokenSettings(config.isShareHardwareTokensAmongUsers());
   }

   @Provides
   Optional<Airlock2FAActivationLetterPrintingService> provideAirlock2FAActivationLetterPrintingService(
      Optional<Airlock2FACreateActivationLettersConfig> config, QrCodeImageReader qrCodeImageReader, ManagedUserRepository userRepository
   ) {
      return config.flatMap(Airlock2FACreateActivationLettersConfig::getLetterPrintingOptions)
         .map(
            c -> new Airlock2FAActivationLetterPrintingServiceImpl(
               userId -> userRepository.getById(userId).getContextData().asKeyValueMap(),
               qrCodeImageReader,
               c.getRenderer(),
               c.getOutputDirectory().toPath(),
               c.getWorkingDirectory().map(File::toPath),
               c.getLanguageContextDataName()
            )
         );
   }

   @Provides
   Optional<Airlock2FACreateActivationLettersConfig> provideAirlock2FACreateActivationLettersConfig(Airlock2FATokenControllerConfig tokenController) {
      return tokenController.getCreateActivationLetters();
   }

   @Provides
   Airlock2FAActivationSettings provideAirlock2FAActivationSettings(
      Optional<Airlock2FACreateActivationLettersConfig> config, Airlock2FASettings airlock2FASettings
   ) {
      Duration validity = config.map(Airlock2FACreateActivationLettersConfig::getEnrollmentValidity)
         .orElseThrow(() -> new ServiceConfigurationException("Airlock 2FA letter creation is not configured"));
      return Airlock2FAActivationSettings.builder()
         .enrollmentTimeout(validity)
         .recoveryFlowBindingEnabled(airlock2FASettings.isTrustedSessionBindingForRecovery())
         .activationFlowBindingEnabled(airlock2FASettings.getTrustedSessionBindingForActivation().isEnabledForLetters())
         .provideActivationCodeShortEnabled(false)
         .build();
   }

   @Provides
   Optional<Airlock2FAActivationLetterService> provideActivationLetterCreationService(
      Optional<Airlock2FACreateActivationLettersConfig> config, Provider<Airlock2FAActivationLetterService> serviceProvider
   ) {
      return config.map(c -> (Airlock2FAActivationLetterService)serviceProvider.get());
   }

   @Provides
   Optional<Airlock2FAShipmentLetterPrintingService> provideAirlock2FAShipmentLetterPrintingService(
      ManagedUserRepository userRepository, Optional<Airlock2FAShipmentLettersConfig> shipmentLetterConfig
   ) {
      return shipmentLetterConfig.map(
         c -> new Airlock2FAShipmentLetterPrintingServiceImpl(
            userRepository, c.getRenderer(), c.getOutputDirectory().toPath(), c.getWorkingDirectory().map(File::toPath), c.getLanguageContextDataName()
         )
      );
   }

   @RequestScoped
   @Provides
   @Named("AIRLOCK_2FA_ACTIVATION_CODE_ENCRYPTION")
   JwtHmacAndAesGcmEncryption provideJwtHmacAndAesGcmEncryption(Airlock2FATokenControllerConfig config) {
      JwtSecret jwtSecret = config.getAirlock2faSettings().getRepository().getStorageEncryptionConfig().getJwtSecret();
      return new JwtHmacAndAesGcmEncryptionImpl(jwtSecret.getJwtKeys());
   }

   @Provides
   Optional<Airlock2FAActivationLetterOrderService> provideActivationLetterOrderService(
      Airlock2FATokenControllerConfig config, Airlock2FAActivationLetterOrderService service
   ) {
      return config.getOrderActivationLetters().map(c -> service);
   }

   @Provides
   Optional<Airlock2FAFindActivationLetterOrderService> provideFindActivationLetterOrderService(
      Airlock2FATokenControllerConfig config, Airlock2FAFindActivationLetterOrderService service
   ) {
      return config.getOrderActivationLetters().map(c -> service);
   }

   @Provides
   Airlock2FAAccessController provideAirlock2FAAccessController(AdminappAccessController accessController) {
      return () -> accessController.isActionAllowed(AdminappAction.VIEW_AIRLOCK_2FA_ACTIVATION_SECRET);
   }

   protected void configure() {
      this.bind(Airlock2FAActivationLetterService.class).to(Airlock2FAActivationLetterServiceImpl.class);
      this.bind(QrCodeImageReader.class).to(QrCodeImageReaderImpl.class);
      this.bind(Airlock2FAUserRepository.class).to(Airlock2FAUserRepositoryImpl.class).in(RequestScoped.class);
      this.expose(Airlock2FAUserRepository.class);
      this.bind(FuturaeRequestFactory.class);
      this.bind(FuturaeAdminApiRequestFactory.class).to(FuturaeAdminApiRequestFactoryImpl.class);
      this.bind(Airlock2FAActivationLetterRepository.class).to(Airlock2FAActivationLetterRepositoryImpl.class);
      this.bind(Airlock2FAActivationLetterOrderRepository.class).to(Airlock2FAActivationLetterOrderRepositoryImpl.class);
      this.bind(Airlock2FAAdminApiUserAccountService.class).to(FuturaeAdminApiApiUserAccountServiceImpl.class);
      this.bind(Airlock2FAUserAccountRepository.class).to(Airlock2FAUserAccountRepositoryImpl.class);
      this.bind(Airlock2FAActivationCodeMapper.class).to(Airlock2FAActivationCodeMapperImpl.class);
      this.bind(Airlock2FAAdminApiDeviceService.class).to(FuturaeAdminApiDeviceServiceImpl.class);
      this.bind(FuturaeAdminApiEnrollmentRequestFactory.class);
      this.bind(Airlock2FAAdminApiEnrollmentService.class).to(FuturaeAdminApiEnrollmentServiceImpl.class);
      this.bind(Airlock2FAHardwareTokenService.class);
      this.expose(Airlock2FAHardwareTokenService.class);
      this.bind(Airlock2FAActivationLetterViewerFactory.class);
      this.expose(Airlock2FAActivationLetterViewerFactory.class);
      this.bind(Airlock2FAAdminApiHardwareTokenService.class).to(FuturaeAdminApiHardwareTokenServiceImpl.class).in(RequestScoped.class);
      this.expose(Airlock2FAAdminApiHardwareTokenService.class);
      this.bind(Airlock2FAAdminApiUserDeleteService.class).to(FuturaeAdminApiUserDeleteServiceImpl.class);
      this.bind(Airlock2FAAdminApiUserService.class).to(FuturaeAdminApiUserServiceImpl.class);
      this.bind(Airlock2FAFindActivationLetterOrderService.class).to(Airlock2FAActivationLetterOrderServiceImpl.class);
      this.bind(Airlock2FAActivationLetterOrderService.class).to(Airlock2FAActivationLetterOrderServiceImpl.class);
      this.bind(Airlock2FAAdminService.class).in(RequestScoped.class);
      this.expose(Airlock2FAAdminService.class);
   }
}
