package com.airlock.iam.common.domain.model.role;

import com.airlock.iam.base.api.domain.model.exception.ViolationException;
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.user.PersistentUser;
import com.airlock.iam.log.api.signing.SigningLogger;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Set;

public final class Roles {
   private final PersistentUser user;
   private final RolesValidator validator;
   private final SigningLogger signingLogger;

   public Roles(PersistentUser user, SigningLogger signingLogger) {
      this(user, RolesValidator.withoutRoleValidation(), signingLogger);
   }

   public boolean has(Role role) {
      return this.getAll().contains(role);
   }

   public boolean hasRoles() {
      return !this.getAll().isEmpty();
   }

   public boolean hasAllOf(RoleCombination combination) {
      return !combination.hasAdditionalRoles(this.getAll());
   }

   public boolean hasAtLeastOneOf(RoleCombination combination) {
      return combination.containsAtLeastOne(this.getAll());
   }

   public void remove(Role role) {
      ValidationResult result = this.validator.validateRemoveRole(role);
      if (!result.isValid()) {
         this.signingLogger
            .log("Could not remove role \"%s\" from user \"%s\" because role is not manageable by current admin", new Object[]{role, this.user.getName()});
         throw new ViolationException("Removing role not allowed: " + result.getFailure());
      }

      this.removeInternal(role);
   }

   private void removeInternal(Role role) {
      this.user.removeRoleByName(role.getRoleName());
      this.signingLogger.log("Removed role \"%s\" from user \"%s\"", new Object[]{role.getRoleName(), this.user.getName()});
   }

   public void remove(RoleCombination combination) {
      ValidationResult result = this.validator.validateAddRoleCombination(combination);
      if (!result.isValid()) {
         this.signingLogger
            .log(
               "Could not remove role combination \"%s\" from user \"%s\" because roles are not manageable by current admin",
               new Object[]{combination.asCommaSeparatedString(), this.user.getName()}
            );
         throw new ViolationException("Removing role not allowed: " + result.getFailure());
      }

      combination.getRoles().forEach(this::removeInternal);
   }

   public void add(Role role) {
      ValidationResult result = this.validator.validateAddRole(role);
      if (!result.isValid()) {
         this.signingLogger
            .log("Could not add role \"%s\" to user \"%s\" because role is not manageable by current admin", new Object[]{role, this.user.getName()});
         throw new ViolationException("Adding role not allowed: " + result.getFailure());
      }

      this.addInternal(role);
   }

   private void addInternal(Role role) {
      this.user.addRole(role);
      this.signingLogger.log("Added role \"%s\" to user \"%s\"", new Object[]{role, this.user.getName()});
   }

   public void add(RoleCombination combination) {
      ValidationResult result = this.validator.validateAddRoleCombination(combination);
      if (!result.isValid()) {
         this.signingLogger
            .log(
               "Could not add role combination \"%s\" to user \"%s\" because role are not manageable by current admin",
               new Object[]{combination.asCommaSeparatedString(), this.user.getName()}
            );
         throw new ViolationException("Adding role combination not allowed: " + result.getFailure());
      }

      combination.getRoles().forEach(this::addInternal);
   }

   public void setRoles(Set<Role> roles) {
      ValidationResult result = this.validator.validateSetRoles(this.getAll(), roles);
      if (!result.isValid()) {
         this.signingLogger
            .log("Could not update roles on user \"%s\" because some roles are not manageable by current admin", new Object[]{this.user.getName()});
         throw new ViolationException("Setting roles not allowed: " + result.getFailure());
      }

      this.user.clearRoles();
      roles.forEach(this::addInternal);
   }

   public void setRoleCombinations(Set<RoleCombination> combinations) {
      ValidationResult result = this.validator.validateSetRoleCombinations(combinations);
      if (!result.isValid()) {
         this.signingLogger
            .log("Could not update roles on user \"%s\" because some roles are not manageable by current admin", new Object[]{this.user.getName()});
         throw new ViolationException("Setting roles not allowed: " + result.getFailure());
      }

      this.user.clearRoles();
      combinations.forEach(combination -> combination.getRoles().forEach(this::addInternal));
   }

   public Set<Role> getAll() {
      return this.user.getRoles();
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public Roles(PersistentUser user, RolesValidator validator, SigningLogger signingLogger) {
      this.user = user;
      this.validator = validator;
      this.signingLogger = signingLogger;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class RolesBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private PersistentUser user;
      @SuppressFBWarnings(justification = "generated code")
      private RolesValidator validator;
      @SuppressFBWarnings(justification = "generated code")
      private SigningLogger signingLogger;

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

      @SuppressFBWarnings(justification = "generated code")
      public Roles.RolesBuilder user(PersistentUser user) {
         this.user = user;
         return this;
      }

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

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

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

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Roles.RolesBuilder(user=" + this.user + ", validator=" + this.validator + ", signingLogger=" + this.signingLogger + ")";
      }
   }
}
