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

import com.airlock.iam.admin.api.domain.model.administrator.CurrentAdministrator;
import com.airlock.iam.admin.application.configuration.AdminappRestConfigSpec;
import com.airlock.iam.admin.application.configuration.mtan.MtanTokenController;
import com.airlock.iam.admin.application.service.token.mtan.MtanTokenService;
import com.airlock.iam.admin.application.service.token.mtan.OneTimePasswordService;
import com.airlock.iam.admin.application.service.token.mtan.OneTimePasswordServiceImpl;
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.mtan.MtanAuthenticationMethod;
import com.airlock.iam.base.api.domain.model.exception.ServiceConfigurationException;
import com.airlock.iam.base.misc.time.TimeProvider;
import com.airlock.iam.common.domain.model.otp.OneTimePasswordFactory;
import com.airlock.iam.common.domain.model.otp.OneTimePasswordFactoryImpl;
import com.airlock.iam.common.domain.model.phone.VisiblePhoneNumberDigits;
import com.airlock.iam.common.domain.model.token.mtan.CommonMtanTokenRepository;
import com.airlock.iam.common.domain.model.token.mtan.MtanTokenRepository;
import com.airlock.iam.common.domain.service.sms.DefaultSmsValidator;
import com.airlock.iam.common.domain.service.sms.SmsService;
import com.airlock.iam.common.domain.service.sms.SmsService.SmsServiceSettings;
import com.airlock.iam.core.domain.model.phone.CountryCallingCode;
import com.airlock.iam.core.domain.model.security.SharedSecret;
import com.airlock.iam.core.misc.authen.SecretStringGenerator;
import com.airlock.iam.core.misc.impl.authen.mtan.MtanSettings;
import com.airlock.iam.core.misc.util.password.hash.PasswordHash;
import com.airlock.iam.log.api.signing.SigningLogger;
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.time.Duration;
import java.util.Optional;

public class MtanTokenServiceModule extends PrivateModule {
   @Provides
   MtanSettings provideMtanSettings(MtanTokenController tokenController) {
      return tokenController.getMtanSettings();
   }

   @Provides
   SmsServiceSettings smsServiceSettings(MtanSettings mtanSettings) {
      return SmsServiceSettings.builder()
         .smsGateway(mtanSettings.getSmsGateway())
         .smsValidator(new DefaultSmsValidator(mtanSettings.getSmsGateway()))
         .configuredOriginator(mtanSettings.getOriginatorName())
         .build();
   }

   @Provides
   VisiblePhoneNumberDigits provideVisiblePhoneNumberDigits(MtanSettings mtanSettings) {
      return VisiblePhoneNumberDigits.of(mtanSettings.getVisiblePhoneNumberDigits());
   }

   @Provides
   CountryCallingCode countryCallingCode(MtanSettings mtanSettings) {
      return mtanSettings.getDefaultCountryCode();
   }

   @Provides
   SecretStringGenerator provideSecretStringGenerator(MtanSettings mtanSettings) {
      return mtanSettings.getOtpGenerator();
   }

   @Provides
   Duration provideOtpValidity(MtanSettings mtanSettings) {
      return mtanSettings.getOtpValidity();
   }

   @Provides
   OneTimePasswordFactory provideOneTimePasswordFactory(MtanSettings mtanSettings, TimeProvider timeProvider) {
      return new OneTimePasswordFactoryImpl(mtanSettings::getOtpGenerator, () -> Duration.ofHours(8L), timeProvider);
   }

   @Provides
   Optional<PasswordHash> providePasswordHash(AdminappRestConfigSpec restConfig) {
      return restConfig.getHashFunction();
   }

   @Provides
   SharedSecret provideSharedSecret(AdminappRestConfigSpec restConfig) {
      return SharedSecret.of(restConfig.getHashSharedSecret().orElse(null));
   }

   @Provides
   CommonMtanTokenRepository commonTokenRepository(MtanSettings mtanSettings) {
      return new CommonMtanTokenRepository(mtanSettings.getMtanHandler());
   }

   @Provides
   MtanTokenRepository provideTokenRepository(
      CurrentAdministrator administrator, MtanSettings mtanSettings, CommonMtanTokenRepository commonTokenRepository, MtanTokenController tokenControllerConfig
   ) {
      return MtanTokenRepository.builder()
         .administratorName(administrator.getName())
         .mtanHandler(mtanSettings.getMtanHandler())
         .commonMtanTokenRepository(commonTokenRepository)
         .letterRenderer(tokenControllerConfig.getSecretLetterRenderer())
         .deletePropertiesWithCredential(tokenControllerConfig.getDeletePropertiesWithCredential())
         .build();
   }

   @Provides
   MtanTokenController credentialController(Provider<TokenController.TokenControllers> tokenControllers) {
      Optional<MtanTokenController> tokenController = ((TokenController.TokenControllers)tokenControllers.get()).ofId("MTAN");
      ServiceConfigurationException.checkPresent(tokenController, "mTAN token controller must be configured.");
      return tokenController.orElseThrow();
   }

   @Provides
   @RequestScoped
   @Named("MtanSmsService")
   SmsService provideSmsService(SmsServiceSettings settings, SigningLogger logger) {
      return SmsService.builder().settings(settings).signingLogger(logger).build();
   }

   @Provides
   @RequestScoped
   @Exposed
   @Named("MTAN")
   AuthenticationMethod provideAuthMethod(MtanSettings mtanSettings, MtanTokenController controller, MtanTokenService service) {
      return MtanAuthenticationMethod.builder()
         .settings(mtanSettings)
         .configuredId(controller.getConfiguredId())
         .autoOrderOnAuthMethodAdd(controller.isAutoOrderCredential())
         .autoOrderOnUserCreate(controller.isAutoOrderForNewUsers())
         .service(service)
         .build();
   }

   protected void configure() {
      this.bind(MtanTokenService.class).in(RequestScoped.class);
      this.expose(MtanTokenService.class);
      this.bind(OneTimePasswordService.class).to(OneTimePasswordServiceImpl.class).in(RequestScoped.class);
   }
}
