package com.airlock.iam.flow.shared.domain.model.condition;

import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.common.api.domain.model.user.User;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.factor.domain.model.airlock2fa.Airlock2FAUserInformation;
import com.airlock.iam.factor.domain.service.airlock2fa.Airlock2FAUserInformationService;
import com.airlock.iam.factor.domain.service.airlock2fa.repository.Airlock2FAUserRepository;
import com.airlock.iam.flow.api.domain.model.step.FlowCondition;
import com.airlock.iam.flow.shared.application.configuration.condition.Airlock2FADeviceDeletionPossibleConditionConfig;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.List;
import java.util.Optional;
import lombok.NonNull;

@LogicForConfig(Airlock2FADeviceDeletionPossibleConditionConfig.class)
public final class Airlock2FADeviceDeletionPossibleCondition implements FlowCondition {
   private static final ELogger LOG = ELoggerFactory.getLogger(Airlock2FADeviceDeletionPossibleCondition.class);
   private final Provider<User> user;
   private final Provider<Airlock2FAUserRepository> repository;
   private final Provider<Airlock2FAUserInformationService> informationService;
   private final Provider<Boolean> canUserDeleteAllDevices;

   @Inject
   Airlock2FADeviceDeletionPossibleCondition(
      @NonNull Provider<User> user,
      @NonNull Provider<Airlock2FAUserRepository> repository,
      @NonNull Provider<Airlock2FAUserInformationService> informationService,
      @NonNull Provider<Boolean> canUserDeleteAllDevices
   ) {
      if (user == null) {
         throw new NullPointerException("user is marked non-null but is null");
      }

      if (repository == null) {
         throw new NullPointerException("repository is marked non-null but is null");
      }

      if (informationService == null) {
         throw new NullPointerException("informationService is marked non-null but is null");
      }

      if (canUserDeleteAllDevices == null) {
         throw new NullPointerException("canUserDeleteAllDevices is marked non-null but is null");
      }

      this.user = user;
      this.repository = repository;
      this.informationService = informationService;
      this.canUserDeleteAllDevices = canUserDeleteAllDevices;
   }

   @Override
   public boolean isFulfilled() {
      UserId userId = ((User)this.user.get()).getId();
      Optional<Integer> numberOfAppDevices = this.numberOfAppDevices(userId);
      if (numberOfAppDevices.isEmpty()) {
         LOG.debug(String.format("User '%s' does not have an Airlock 2FA account or does not have any registered devices", userId));
         return false;
      } else {
         return this.hasEnoughDevices(userId, numberOfAppDevices.get());
      }
   }

   private boolean hasEnoughDevices(UserId userId, int numberOfAppDevices) {
      int minimumNumberOfDevices = this.canUserDeleteAllDevices.get() ? 1 : 2;
      boolean hasEnoughDevices = numberOfAppDevices >= minimumNumberOfDevices;
      if (!hasEnoughDevices) {
         LOG.debug(
            String.format(
               "User '%s' has only %d Airlock 2FA devices, but at least %d are required to be able to delete an app device",
               userId,
               numberOfAppDevices,
               minimumNumberOfDevices
            )
         );
      }

      return hasEnoughDevices;
   }

   private Optional<Integer> numberOfAppDevices(UserId userId) {
      return ((Airlock2FAUserRepository)this.repository.get())
         .findBy(userId)
         .flatMap(airlock2FAUser -> this.informationService().getUser(airlock2FAUser.getAccountId()))
         .map(Airlock2FAUserInformation::getAppDevices)
         .map(List::size);
   }

   private Airlock2FAUserInformationService informationService() {
      return (Airlock2FAUserInformationService)this.informationService.get();
   }
}
