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

import com.airlock.iam.base.api.infrastructure.plugin.ModuleForConfig;
import com.airlock.iam.base.api.infrastructure.plugin.PluginConfigToLogic;
import com.airlock.iam.base.misc.time.TimeProvider;
import com.airlock.iam.common.api.domain.model.store.user.UserStore;
import com.airlock.iam.common.api.domain.model.user.transformation.UsernameTransformation;
import com.airlock.iam.common.application.configuration.credential.BasicAuthRequestAuthenticationConfig;
import com.airlock.iam.common.domain.model.credential.BasicAuthSettings;
import com.airlock.iam.common.domain.model.password.repository.UserPasswordRepository;
import com.airlock.iam.common.domain.model.principal.MaxFailedAttempts;
import com.airlock.iam.common.domain.model.principal.UserPrincipal;
import com.airlock.iam.common.domain.model.principal.UserPrincipalImpl;
import com.airlock.iam.common.domain.model.transform.UsernameTransformerUsernameTransformation;
import com.airlock.iam.common.domain.service.password.UsernamePasswordAuthenticationService;
import com.airlock.iam.common.domain.service.password.UsernamePasswordAuthenticationServiceImpl;
import com.airlock.iam.common.domain.service.principal.UserPrincipalService;
import com.airlock.iam.common.infrastructure.contextdata.ContextDataFactoryImpl;
import com.airlock.iam.common.infrastructure.service.PersistentUserPrincipalService;
import com.airlock.iam.common.infrastructure.service.VirtualUserPrincipalService;
import com.airlock.iam.log.api.signing.SigningLogger;
import com.google.inject.Exposed;
import com.google.inject.PrivateModule;
import com.google.inject.Provides;
import jakarta.inject.Named;
import jakarta.inject.Provider;
import java.nio.charset.Charset;
import java.util.Optional;

@ModuleForConfig(BasicAuthRequestAuthenticationConfig.class)
public final class BasicAuthRequestAuthenticationModule extends PrivateModule {
   private static final String BASIC_AUTH_REQUEST_AUTHENTICATION_USER_PRINCIPAL = "BasicAuthRequestAuthenticationUserPrincipal";

   @Provides
   UserPasswordRepository provideUserPasswordRepository(BasicAuthRequestAuthenticationConfig config, PluginConfigToLogic configToLogic) {
      return configToLogic.getLogic(config.getPasswordRepository());
   }

   @Provides
   @Exposed
   UsernameTransformation provideUsernameTransformation(BasicAuthRequestAuthenticationConfig config) {
      return new UsernameTransformerUsernameTransformation(config.getUsernameTransformers());
   }

   @Provides
   UsernamePasswordAuthenticationServiceImpl.UsernamePasswordAuthSettings provideUsernamePasswordAuthConfig(BasicAuthRequestAuthenticationConfig config) {
      return UsernamePasswordAuthenticationServiceImpl.UsernamePasswordAuthSettings.builder().policyToCheckOnLogin(config.getPolicyToCheckOnLogin()).build();
   }

   @Provides
   @Exposed
   BasicAuthSettings provideBasicAuthSettings(BasicAuthRequestAuthenticationConfig config) {
      return new BasicAuthSettings(Charset.forName(config.getCharsetName()));
   }

   @Provides
   @Named("BasicAuthRequestAuthenticationUserPrincipal")
   UserPrincipal provideUserPrincipal(Provider<MaxFailedAttempts> maxFailedAttempts, SigningLogger signingLogger, TimeProvider timeProvider) {
      return new UserPrincipalImpl(ContextDataFactoryImpl::new, maxFailedAttempts, signingLogger, timeProvider);
   }

   @Provides
   @Exposed
   UsernamePasswordAuthenticationService provideUsernamePasswordAuthenticationService(
      UserPasswordRepository passwordRepository,
      UsernamePasswordAuthenticationServiceImpl.UsernamePasswordAuthSettings settings,
      @Named("BasicAuthRequestAuthenticationUserPrincipal") UserPrincipal userPrincipal,
      SigningLogger signingLogger,
      BasicAuthRequestAuthenticationConfig config
   ) {
      return new UsernamePasswordAuthenticationServiceImpl(
         () -> passwordRepository, () -> settings, () -> userPrincipal, createUserPrincipalService(config.getUserStore(), userPrincipal), signingLogger
      );
   }

   private static Provider<UserPrincipalService> createUserPrincipalService(Optional<UserStore> userStore, UserPrincipal userPrincipal) {
      return userStore.isPresent()
         ? () -> new PersistentUserPrincipalService(userStore::get, () -> userPrincipal)
         : () -> new VirtualUserPrincipalService(() -> userPrincipal);
   }

   @Provides
   @Exposed
   @Named("BasicAuthRequestAuthenticationUserPrincipalService")
   UserPrincipalService provideUserPrincipalService(
      BasicAuthRequestAuthenticationConfig config, @Named("BasicAuthRequestAuthenticationUserPrincipal") UserPrincipal userPrincipal
   ) {
      return (UserPrincipalService)createUserPrincipalService(config.getUserStore(), userPrincipal).get();
   }

   protected void configure() {
   }
}
