package com.airlock.iam.flow.shared.infrastructure.factory;

import com.airlock.iam.base.api.domain.model.exception.ServiceConfigurationException;
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.PersistentUser;
import com.airlock.iam.common.domain.model.contextdata.ContextDataFactory;
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.user.PrincipalInfo;
import com.airlock.iam.common.domain.service.principal.UserPrincipalService;
import com.airlock.iam.common.infrastructure.service.InternalUserPrincipalService;
import com.airlock.iam.common.infrastructure.service.PersistentUserPrincipalService;
import com.airlock.iam.common.infrastructure.service.VirtualUserPrincipalService;
import com.airlock.iam.flow.application.configuration.FlowConfig;
import com.airlock.iam.flow.shared.domain.model.user.UserLoginStatistics;
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.Provider;
import java.util.Optional;

public final class UserPrincipalModule extends PrivateModule {
   @Exposed
   @Provides
   Optional<UserLoginStatistics> provideUserLoginStatistics(UserPrincipal principal) {
      if (!principal.exists()) {
         return Optional.empty();
      }

      PersistentUser user = principal.user();
      return Optional.of(
         UserLoginStatistics.builder()
            .latestSuccessfulLogin(user.getLatestSuccessfulLogin())
            .secondLatestSuccessfulLogin(user.getSecondLatestSuccessfulLogin())
            .latestLoginAttempt(user.getLatestLoginAttempt())
            .firstLogin(user.getFirstLogin())
            .totalLogins(user.getTotalLogins())
            .failedLogins(user.getFailedLogins())
            .failedLoginsBeforeLatestSuccessfulLogin(user.getFailedLoginsBeforeLatestSuccessfulLogin())
            .selfRegistered(user.isSelfRegistered())
            .selfRegistrationDate(user.getSelfRegistrationDate())
            .locked(user.isLocked())
            .lockReason(user.getLockReason())
            .lockDate(user.getLockDate())
            .latestUnlockAttempt(user.getLatestUnlockAttempt())
            .unlockAttempts(user.getUnlockAttempts())
            .valid(user.getUserValidity().isValid())
            .validFrom(user.getValidFrom())
            .validTo(user.getValidTo())
            .build()
      );
   }

   @Exposed
   @Provides
   @RequestScoped
   UserPrincipal provideUserPrincipal(
      Provider<ContextDataFactory> contextDataFactory,
      Provider<MaxFailedAttempts> maxFailedAttempts,
      SigningLogger signingLogger,
      TimeProvider timeProvider,
      InternalUserPrincipalService internalUserPrincipalService,
      PrincipalInfo principalInfo
   ) {
      UserPrincipalImpl userPrincipal = new UserPrincipalImpl(contextDataFactory, maxFailedAttempts, signingLogger, timeProvider);
      principalInfo.getKnownIdentity().ifPresent(identity -> internalUserPrincipalService.internalInitialize(identity, userPrincipal));
      return userPrincipal;
   }

   @Provides
   @RequestScoped
   InternalUserPrincipalService provideInternalUserPrincipalService(
      Optional<FlowConfig> currentFlowConfig, Provider<UserPrincipal> userPrincipal, Optional<UserStore> userStore
   ) {
      if (currentFlowConfig.isPresent() && currentFlowConfig.get().requiresUserPersistency()) {
         return new PersistentUserPrincipalService(
            () -> ServiceConfigurationException.checkPresent(userStore, "Current flow requires a user store"), userPrincipal
         );
      } else {
         return !userStore.isEmpty() && (!currentFlowConfig.isPresent() || currentFlowConfig.get().requiresUserPersistency())
            ? new PersistentUserPrincipalService(userStore::get, userPrincipal)
            : new VirtualUserPrincipalService(userPrincipal);
      }
   }

   @Provides
   @RequestScoped
   @Exposed
   UserPrincipalService provideUserPrincipalService(InternalUserPrincipalService internalUserPrincipalService) {
      return internalUserPrincipalService;
   }

   @Provides
   @Exposed
   @RequestScoped
   Optional<UserPrincipalService> provideOptionalUserPrincipalService(UserPrincipalService service) {
      return Optional.of(service);
   }

   protected void configure() {
   }
}
