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

import com.airlock.iam.base.api.domain.model.validation.GenericError;
import com.airlock.iam.base.api.domain.model.validation.ValidationResult;
import com.airlock.iam.base.api.domain.model.validation.ValidatorError;
import com.airlock.iam.common.api.domain.model.role.Role;
import com.google.common.collect.Sets;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.List;
import java.util.Optional;
import java.util.Set;

public class RolesValidator {
   private final Optional<Set<Role>> availableRoles;
   private final Optional<Set<RoleCombination>> availableRoleCombinations;

   public static RolesValidator withoutRoleValidation() {
      return new RolesValidator(Optional.empty(), Optional.empty());
   }

   public ValidationResult validateAddRoleCombination(RoleCombination combination) {
      return this.validate(combination, GenericError.NOT_ADDABLE);
   }

   public ValidationResult validateRemoveRoleCombination(RoleCombination combination) {
      return this.validate(combination, GenericError.NOT_REMOVABLE);
   }

   public ValidationResult validateAddRole(Role role) {
      return this.validate(role, GenericError.NOT_ADDABLE);
   }

   public ValidationResult validateRemoveRole(Role role) {
      return this.validate(role, GenericError.NOT_REMOVABLE);
   }

   public ValidationResult validateSetRoles(Set<Role> oldRoles, Set<Role> newRoles) {
      ValidationResult result = ValidationResult.valid();
      Sets.difference(newRoles, oldRoles).forEach(role -> result.mergeWith(this.validateAddRole(role)));
      Sets.difference(oldRoles, newRoles).forEach(role -> result.mergeWith(this.validateRemoveRole(role)));
      return result;
   }

   public ValidationResult validateSetRoleCombinations(Set<RoleCombination> combinations) {
      ValidationResult result = ValidationResult.valid();
      combinations.forEach(combination -> result.mergeWith(this.validateAddRoleCombination(combination)));
      return result;
   }

   private ValidationResult validate(Role role, GenericError error) {
      return this.availableRoles.isPresent() && !this.availableRoles.get().contains(role)
         ? ValidationResult.invalid(roleValidationError(role.formatRole(), error))
         : ValidationResult.valid();
   }

   private ValidationResult validate(RoleCombination combination, GenericError error) {
      return this.availableRoleCombinations.isPresent() && !this.availableRoleCombinations.get().contains(combination)
         ? ValidationResult.invalid(roleValidationError(combination.asCommaSeparatedString(), error))
         : ValidationResult.valid();
   }

   private static List<ValidatorError> roleValidationError(String role, GenericError type) {
      return List.of(ValidatorError.validatorErrorBuilder("role", type).parameter("role", role).build());
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public RolesValidator(Optional<Set<Role>> availableRoles, Optional<Set<RoleCombination>> availableRoleCombinations) {
      this.availableRoles = availableRoles;
      this.availableRoleCombinations = availableRoleCombinations;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class RolesValidatorBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Optional<Set<Role>> availableRoles;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<Set<RoleCombination>> availableRoleCombinations;

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

      @SuppressFBWarnings(justification = "generated code")
      public RolesValidator.RolesValidatorBuilder availableRoles(Optional<Set<Role>> availableRoles) {
         this.availableRoles = availableRoles;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RolesValidator.RolesValidatorBuilder availableRoleCombinations(Optional<Set<RoleCombination>> availableRoleCombinations) {
         this.availableRoleCombinations = availableRoleCombinations;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public RolesValidator build() {
         return new RolesValidator(this.availableRoles, this.availableRoleCombinations);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "RolesValidator.RolesValidatorBuilder(availableRoles="
            + this.availableRoles
            + ", availableRoleCombinations="
            + this.availableRoleCombinations
            + ")";
      }
   }
}
