package com.airlock.iam.oauth2.domain.model.session;

import com.airlock.iam.base.api.domain.model.exception.FailureException;
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.base.misc.log.LogUtil;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;

public final class OAuth2RuleBasedCustomSessionAttributeValidator implements OAuth2CustomSessionAttributeValidator {
   private final Map<String, OAuth2CustomSessionAttributeValidatorRule> rules;

   private OAuth2RuleBasedCustomSessionAttributeValidator(Map<String, OAuth2CustomSessionAttributeValidatorRule> rules) {
      this.rules = rules;
   }

   public static OAuth2CustomSessionAttributeValidator sessionAttributeValidatorWithRules(Map<String, OAuth2CustomSessionAttributeValidatorRule> rules) {
      return new OAuth2RuleBasedCustomSessionAttributeValidator(rules);
   }

   @Override
   public ValidationResult validateAddAttribute(Set<OAuth2CustomSessionAttribute> newAttributes, Set<OAuth2CustomSessionAttribute> existingAttributes) {
      List<ValidatorError> validatorErrors = new ArrayList<>();
      Set<OAuth2CustomSessionAttribute> currentAttributes = new HashSet<>(existingAttributes);

      for (OAuth2CustomSessionAttribute newAttribute : newAttributes) {
         ValidationResult nameValidationResult = this.validateName(newAttribute.getName());
         if (nameValidationResult.isNotValid()) {
            validatorErrors.addAll(nameValidationResult.getErrors());
         } else {
            ValidationResult valueValidationResult = this.validateValue(newAttribute);
            if (valueValidationResult.isNotValid()) {
               validatorErrors.addAll(valueValidationResult.getErrors());
            } else {
               ValidationResult uniqueNamesValidationResult = this.validateUniqueNames(newAttribute.getName(), currentAttributes);
               if (uniqueNamesValidationResult.isNotValid()) {
                  validatorErrors.addAll(uniqueNamesValidationResult.getErrors());
               } else {
                  currentAttributes.add(newAttribute);
               }
            }
         }
      }

      return !validatorErrors.isEmpty() ? ValidationResult.invalid(validatorErrors) : ValidationResult.valid();
   }

   @Override
   public ValidationResult validateDeleteAttribute(Set<String> attributeNames, Set<OAuth2CustomSessionAttribute> existingAttributes) {
      Set<OAuth2CustomSessionAttribute> currentAttributes = new HashSet<>(existingAttributes);
      ArrayList<ValidatorError> validatorErrors = new ArrayList<>();

      for (String attributeName : attributeNames) {
         ValidationResult attributeExistsValidationResult = this.validateAttributeExists(attributeName, currentAttributes);
         if (attributeExistsValidationResult.isNotValid()) {
            validatorErrors.addAll(attributeExistsValidationResult.getErrors());
         } else {
            ValidationResult nameValidationResult = this.validateName(attributeName);
            if (nameValidationResult.isNotValid()) {
               validatorErrors.addAll(nameValidationResult.getErrors());
            } else {
               ValidationResult updatableValidationResult = this.validateUpdatable(attributeName);
               if (updatableValidationResult.isNotValid()) {
                  validatorErrors.addAll(updatableValidationResult.getErrors());
               } else {
                  currentAttributes.removeIf(a -> a.getName().equals(attributeName));
               }
            }
         }
      }

      return !validatorErrors.isEmpty() ? ValidationResult.invalid(validatorErrors) : ValidationResult.valid();
   }

   private ValidationResult validateName(String attributeName) {
      OAuth2CustomSessionAttributeValidatorRule rule = this.rules.get(attributeName);
      return rule == null ? ValidationResult.singleError(attributeName, GenericError.UNEXPECTED) : ValidationResult.valid();
   }

   private ValidationResult validateValue(OAuth2CustomSessionAttribute newAttribute) {
      OAuth2CustomSessionAttributeValidatorRule rule = this.rules.get(newAttribute.getName());
      if (rule == null) {
         throw new FailureException(String.format("Unknown OAuth 2.0 session attribute with name %s", LogUtil.quote(newAttribute.getName())));
      }

      Matcher matcher = rule.getPattern().matcher(newAttribute.getValue());
      return !matcher.matches() ? ValidationResult.singleError(newAttribute.getName(), GenericError.WRONG_FORMAT) : ValidationResult.valid();
   }

   private ValidationResult validateUniqueNames(String attributeName, Set<OAuth2CustomSessionAttribute> attributes) {
      Optional<OAuth2CustomSessionAttribute> conflictingAttribute = attributes.stream().filter(item -> item.getName().equals(attributeName)).findAny();
      return conflictingAttribute.isPresent() ? ValidationResult.singleError(attributeName, GenericError.CONFLICT) : ValidationResult.valid();
   }

   private ValidationResult validateAttributeExists(String attributeName, Set<OAuth2CustomSessionAttribute> attributes) {
      Optional<OAuth2CustomSessionAttribute> existingAttribute = attributes.stream().filter(item -> item.getName().equals(attributeName)).findAny();
      return existingAttribute.isEmpty() ? ValidationResult.singleError(attributeName, GenericError.NOT_FOUND) : ValidationResult.valid();
   }

   private ValidationResult validateUpdatable(String attributeName) {
      OAuth2CustomSessionAttributeValidatorRule rule = this.rules.get(attributeName);
      if (rule == null) {
         throw new FailureException(String.format("Unknown OAuth 2.0 session attribute with name %s", LogUtil.quote(attributeName)));
      } else {
         return !rule.isUpdatable() ? ValidationResult.singleError(attributeName, GenericError.READ_ONLY) : ValidationResult.valid();
      }
   }
}
