package com.airlock.iam.admin.application.service.role;

import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.common.api.domain.model.role.Role;
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.event.UserRolesChangedEventFactory;
import com.airlock.iam.common.domain.model.role.Roles;
import com.airlock.iam.common.domain.model.role.RolesValidator;
import com.airlock.iam.log.api.signing.SigningLogger;
import com.google.common.base.Preconditions;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;
import java.util.Set;

public final class RoleService {
   private final UserStore userStore;
   private final RolesValidator validator;
   private final SigningLogger signingLogger;
   private final Provider<UserRolesChangedEventFactory> eventFactory;

   @Inject
   public RoleService(UserStore userStore, RolesValidator validator, SigningLogger signingLogger, Provider<UserRolesChangedEventFactory> eventFactory) {
      this.userStore = userStore;
      this.validator = validator;
      this.signingLogger = signingLogger;
      this.eventFactory = eventFactory;
   }

   public boolean hasUserRole(UserId userId, Role role) {
      Preconditions.checkNotNull(role);
      PersistentUser user = this.getUserWithId(userId);
      return this.roles(user).has(role);
   }

   public boolean addRoleToUser(UserId userId, Role role) {
      Preconditions.checkNotNull(role);
      PersistentUser user = this.getUserWithId(userId);
      Roles roles = this.roles(user);
      if (roles.has(role)) {
         this.signingLogger.log("Could not add role \"%s\" to user \"%s\" because role already exists", new Object[]{role, userId});
         return false;
      } else {
         this.changeRolesAndPublish(user, roles, () -> roles.add(role));
         this.signingLogger.log("Added role \"%s\" to user \"%s\"", new Object[]{role, userId});
         return true;
      }
   }

   public Set<Role> setUserRoles(UserId userId, Set<Role> roles) {
      Preconditions.checkNotNull(roles);
      PersistentUser user = this.getUserWithId(userId);
      Roles userRoles = this.roles(user);
      this.changeRolesAndPublish(user, userRoles, () -> userRoles.setRoles(roles));
      this.signingLogger.log("Set role(s) %s on user \"%s\"", new Object[]{roles, userId});
      return userRoles.getAll();
   }

   public ValidationResult validateSetRoles(UserId userId, Set<Role> roles) {
      return this.validator.validateSetRoles(this.getUserRoles(userId), roles);
   }

   public Set<Role> getUserRoles(UserId userId) {
      return this.roles(this.getUserWithId(userId)).getAll();
   }

   public boolean removeRoleFromUser(UserId userId, Role role) {
      PersistentUser user = this.getUserWithId(userId);
      Roles roles = this.roles(user);
      if (!roles.has(role)) {
         this.signingLogger.log("Could not remove role \"%s\" from user \"%s\" because role is not present", new Object[]{role, userId});
         return false;
      } else {
         this.changeRolesAndPublish(user, roles, () -> roles.remove(role));
         this.signingLogger.log("Removed role \"%s\" from user \"%s\"", new Object[]{role, userId});
         return true;
      }
   }

   private void changeRolesAndPublish(PersistentUser user, Roles roles, Runnable action) {
      Set<Role> oldRoles = Set.copyOf(roles.getAll());
      action.run();
      Set<Role> newRoles = Set.copyOf(roles.getAll());
      this.userStore.update(user);
      ((UserRolesChangedEventFactory)this.eventFactory.get()).publishUserRolesChangedEvent(UserId.userId(user.getName()), oldRoles, newRoles);
   }

   public ValidationResult validateAddRole(Role role) {
      return this.validator.validateAddRole(role);
   }

   public ValidationResult validateRemoveRole(Role role) {
      return this.validator.validateRemoveRole(role);
   }

   private PersistentUser getUserWithId(UserId userId) {
      return (PersistentUser)this.userStore.getByIdentity(userId.getValue());
   }

   private Roles roles(PersistentUser user) {
      return new Roles(user, this.validator, this.signingLogger);
   }

   public static RoleService.RoleServiceBuilder builder() {
      return new RoleService.RoleServiceBuilder().validator(new RolesValidator(Optional.empty(), Optional.empty()));
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class RoleServiceBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private UserStore userStore;
      @SuppressFBWarnings(justification = "generated code")
      private RolesValidator validator;
      @SuppressFBWarnings(justification = "generated code")
      private SigningLogger signingLogger;
      @SuppressFBWarnings(justification = "generated code")
      private Provider<UserRolesChangedEventFactory> eventFactory;

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

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

      @SuppressFBWarnings(justification = "generated code")
      public RoleService.RoleServiceBuilder validator(RolesValidator validator) {
         this.validator = validator;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public RoleService.RoleServiceBuilder eventFactory(Provider<UserRolesChangedEventFactory> eventFactory) {
         this.eventFactory = eventFactory;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "RoleService.RoleServiceBuilder(userStore="
            + this.userStore
            + ", validator="
            + this.validator
            + ", signingLogger="
            + this.signingLogger
            + ", eventFactory="
            + this.eventFactory
            + ")";
      }
   }
}
