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

import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.common.api.domain.model.locking.LockReason;
import com.airlock.iam.common.api.domain.model.store.user.UserStore;
import com.airlock.iam.common.domain.model.event.UserEventFactory;
import com.airlock.iam.common.domain.model.locking.LockReasons;
import com.airlock.iam.common.domain.model.user.Lockable;
import com.airlock.iam.common.domain.model.user.UserLockState;
import com.airlock.iam.common.domain.service.adapt.PersistentUserAdapter;
import com.airlock.iam.common.domain.service.token.UserSessionTokenService;
import com.airlock.iam.log.api.signing.SigningLogger;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import lombok.NonNull;

public class UserLockServiceImpl implements UserLockService {
   private final UserStore userStore;
   private final PersistentUserAdapter<Lockable> adapter;
   private final SigningLogger signingLogger;
   private final UserEventFactory eventFactory;
   private final Provider<UserSessionTokenService> userSessionTokenService;

   @Inject
   public UserLockServiceImpl(
      UserStore userStore,
      PersistentUserAdapter<Lockable> adapter,
      SigningLogger signingLogger,
      UserEventFactory eventFactory,
      Provider<UserSessionTokenService> userSessionTokenService
   ) {
      this.userStore = userStore;
      this.adapter = adapter;
      this.signingLogger = signingLogger;
      this.eventFactory = eventFactory;
      this.userSessionTokenService = userSessionTokenService;
   }

   @Override
   public UserLockState getUserLockState(@NonNull UserId userId) {
      if (userId == null) {
         throw new NullPointerException("userId is marked non-null but is null");
      }

      Lockable lockable = this.adapter.to(this.userStore.getByIdentity(userId.getValue()));
      return lockable.lockState();
   }

   @Override
   public UserLockState lockUser(@NonNull UserId userId, @NonNull String reason) {
      if (userId == null) {
         throw new NullPointerException("userId is marked non-null but is null");
      } else if (reason == null) {
         throw new NullPointerException("reason is marked non-null but is null");
      } else {
         return this.lockUser(userId, LockReasons.toLockReason(reason));
      }
   }

   @Override
   public UserLockState lockUser(@NonNull UserId userId, @NonNull LockReason reason) {
      if (userId == null) {
         throw new NullPointerException("userId is marked non-null but is null");
      } else if (reason == null) {
         throw new NullPointerException("reason is marked non-null but is null");
      } else {
         Lockable lockable = this.adapter.to(this.userStore.getByIdentity(userId.getValue()));
         if (lockable.lockState().isLocked()) {
            this.signingLogger.log("Received request to lock user \"%s\". But user was already locked.", new Object[]{userId});
            return lockable.lockState();
         } else {
            lockable.lock(reason);
            ((UserSessionTokenService)this.userSessionTokenService.get()).handleUserLocked(userId);
            this.userStore.update(this.adapter.from(lockable));
            this.signingLogger.log("Locked user \"%s\" with reason %s", new Object[]{userId, reason.getResourceKey()});
            this.eventFactory.publishUserLockedEvent(userId, reason);
            return lockable.lockState();
         }
      }
   }

   @Override
   public UserLockState unlockUser(@NonNull UserId userId) {
      if (userId == null) {
         throw new NullPointerException("userId is marked non-null but is null");
      } else {
         Lockable lockable = this.adapter.to(this.userStore.getByIdentity(userId.getValue()));
         if (!lockable.lockState().isLocked()) {
            this.signingLogger.log("Received request to unlock user \"%s\". But user \"%s\" was not locked.", new Object[]{userId, userId});
            return lockable.lockState();
         } else {
            lockable.unlock();
            this.userStore.update(this.adapter.from(lockable));
            this.signingLogger.log("Unlocked user \"%s\"", new Object[]{userId});
            this.eventFactory.publishUserUnlockedEvent(userId);
            return lockable.lockState();
         }
      }
   }

   @Override
   public boolean isLockedForReason(@NonNull UserId userId, @NonNull LockReason reason) {
      if (userId == null) {
         throw new NullPointerException("userId is marked non-null but is null");
      }

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

      UserLockState lockState = this.adapter.to(this.userStore.getByIdentity(userId.getValue())).lockState();
      return lockState.isLocked() && lockState.equalReason(reason);
   }

   public UserLockService withDedicatedUserStore(UserStore newUserStore) {
      return new UserLockServiceImpl(newUserStore, this.adapter, this.signingLogger, this.eventFactory, this.userSessionTokenService);
   }

   @SuppressFBWarnings(justification = "generated code")
   public static UserLockServiceImpl.UserLockServiceImplBuilder builder() {
      return new UserLockServiceImpl.UserLockServiceImplBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class UserLockServiceImplBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private UserStore userStore;
      @SuppressFBWarnings(justification = "generated code")
      private PersistentUserAdapter<Lockable> adapter;
      @SuppressFBWarnings(justification = "generated code")
      private SigningLogger signingLogger;
      @SuppressFBWarnings(justification = "generated code")
      private UserEventFactory eventFactory;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<UserSessionTokenService> userSessionTokenService;

      @SuppressFBWarnings(justification = "generated code")
      UserLockServiceImplBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserLockServiceImpl.UserLockServiceImplBuilder userStore(UserStore userStore) {
         this.userStore = userStore;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserLockServiceImpl.UserLockServiceImplBuilder adapter(PersistentUserAdapter<Lockable> adapter) {
         this.adapter = adapter;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserLockServiceImpl.UserLockServiceImplBuilder signingLogger(SigningLogger signingLogger) {
         this.signingLogger = signingLogger;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserLockServiceImpl.UserLockServiceImplBuilder eventFactory(UserEventFactory eventFactory) {
         this.eventFactory = eventFactory;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserLockServiceImpl.UserLockServiceImplBuilder userSessionTokenService(Provider<UserSessionTokenService> userSessionTokenService) {
         this.userSessionTokenService = userSessionTokenService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public UserLockServiceImpl build() {
         return new UserLockServiceImpl(this.userStore, this.adapter, this.signingLogger, this.eventFactory, this.userSessionTokenService);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "UserLockServiceImpl.UserLockServiceImplBuilder(userStore="
            + this.userStore
            + ", adapter="
            + this.adapter
            + ", signingLogger="
            + this.signingLogger
            + ", eventFactory="
            + this.eventFactory
            + ", userSessionTokenService="
            + this.userSessionTokenService
            + ")";
      }
   }
}
