package com.airlock.iam.admin.domain.service.user;

import com.airlock.iam.admin.api.domain.model.user.ManagedUser;
import com.airlock.iam.admin.api.domain.service.user.ManagedUserRepository;
import com.airlock.iam.admin.domain.model.user.ManagedUserImpl;
import com.airlock.iam.admin.domain.model.user.Restrictions;
import com.airlock.iam.base.api.domain.model.exception.ViolationException;
import com.airlock.iam.base.api.domain.model.store.query.Restriction;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.misc.adapt.Adapter;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.store.user.UserStore;
import com.airlock.iam.common.api.domain.model.store.user.capability.UserStoreCapabilities;
import com.airlock.iam.common.api.domain.model.store.user.exception.UserEntityNotFoundException;
import com.airlock.iam.common.api.domain.model.store.user.exception.UserEntityNotUniqueException;
import com.airlock.iam.common.api.domain.model.store.user.schema.UserSchema;
import com.airlock.iam.common.api.domain.model.user.PersistentUser;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ManagedUserRepositoryImpl implements ManagedUserRepository {
   private final UserStore userStore;
   private final Restrictions restrictions;

   @Inject
   public ManagedUserRepositoryImpl(@Named("MANAGED_USERS_STORE") UserStore userStore, Restrictions restrictions) {
      this.userStore = userStore;
      this.restrictions = restrictions;
   }

   @Override
   public ManagedUser getById(UserId username) {
      return this.findById(username).orElseThrow(() -> UserEntityNotFoundException.userNotFound(username));
   }

   @Override
   public Optional<ManagedUser> findById(UserId userId) {
      if (this.restrictions.getRestrictions().isEmpty()) {
         return this.userStore.findByIdentity(userId.getValue()).map(ManagedUserImpl::new);
      } else if (UserStoreCapabilities.COMPLEX_QUERIES.isSupportedBy(this.userStore)) {
         return this.findUserByComplexQuery(userId);
      } else if (this.restrictions.getRestrictions().size() == 1) {
         return this.findUserByNonComplexQuery(userId);
      } else {
         throw new ViolationException(String.format("Unsupported action: Multiple restrictions on none-complex user store %s", LogUtil.quote(this.userStore)));
      }
   }

   private Optional<ManagedUser> findUserByComplexQuery(UserId userId) {
      Stream<Restriction> allRestrictions = Stream.concat(Stream.of(this.userIdRestriction(userId)), this.restrictionsFromProviders());
      List<PersistentUser> users = this.userStore.find().whereAllOf(allRestrictions.toArray(Restriction[]::new)).limit(2).list();
      this.checkUniqueUsers(users);
      return users.stream().findFirst().map(ManagedUserImpl::new);
   }

   private Optional<ManagedUser> findUserByNonComplexQuery(UserId userId) {
      List<String> users = this.userStore
         .find()
         .where(this.userIdRestriction(userId))
         .and(this.restrictions.getRestrictions().getFirst())
         .select(((UserSchema)this.userStore.schema()).identity().selector())
         .limit(2)
         .list();
      this.checkUnique(users);
      return users.stream().findFirst().map(UserId::userId).<PersistentUser>flatMap(this.userStore::findByIdentity).map(ManagedUserImpl::new);
   }

   private Restriction userIdRestriction(UserId userId) {
      return ((UserSchema)this.userStore.schema()).identity().equal(userId.getValue());
   }

   private Stream<Restriction> restrictionsFromProviders() {
      return this.restrictions.getRestrictions().stream();
   }

   private void checkUniqueUsers(List<PersistentUser> users) {
      this.checkUnique(users.stream().<String>map(PersistentUser::getName).collect(Collectors.toList()));
   }

   private void checkUnique(List<String> users) {
      if (users.size() > 1) {
         throw UserEntityNotUniqueException.userEntityNotUniqueExceptionWithMessage(
            String.format("User %s in store %s not unique.", LogUtil.logSafeQuote(users.getFirst()), LogUtil.quote(this.userStore))
         );
      }
   }

   @Override
   public void update(ManagedUser user) {
      this.userStore.update((PersistentUser)Adapter.adapt(user).to(PersistentUser.class));
   }
}
