package com.airlock.iam.common.infrastructure.exception;

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.ValidationResultType;
import com.airlock.iam.base.api.domain.model.validation.ValidatorError;
import com.airlock.iam.base.infrastructure.validation.ConstraintViolationType;
import com.google.common.base.Preconditions;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Path;
import jakarta.validation.Path.Node;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

public final class ConstraintViolationMapper {
   private static final String LIST_ELEMENT = "<list element>";
   private static final String ITERABLE_ELEMENT = "<iterable element>";
   private static final String MAP_KEY = "<map key>";
   private static final String MAP_VALUE = "<map value>";

   private ConstraintViolationMapper() {
   }

   public static ValidatorError mapConstraintViolationToError(ConstraintViolation<?> violation, boolean originatesFromConstraintViolationException) {
      Optional<Node> leafNode = getLeafNode(violation.getPropertyPath());
      String sourcePointer = calculateSourcePointer(violation.getPropertyPath(), originatesFromConstraintViolationException);
      return leafNode.isPresent() && isViolationErrorForMapKey(leafNode.get())
         ? ValidatorError.validatorErrorBuilder(sourcePointer, GenericError.INVALID_ELEMENT_NAME).parameters(Map.of("name", leafNode.get().getKey())).build()
         : ValidatorError.validatorErrorBuilder(sourcePointer, calculateType(violation).orElse(null))
            .parameters((Map<? extends String, ? extends Object>)calculateParameters(violation.getConstraintDescriptor().getAnnotation()))
            .build();
   }

   private static Optional<Node> getLeafNode(Path propertyPath) {
      return StreamSupport.<Node>stream(propertyPath.spliterator(), false).reduce((first, second) -> second);
   }

   private static boolean isViolationErrorForMapKey(Node node) {
      return node.getName().equals("<map key>");
   }

   private static Map<String, ?> calculateParameters(Annotation annotation) {
      Optional<ConstraintViolationType> type = ConstraintViolationType.getForAnnotation(annotation);
      return type.isEmpty() ? Collections.emptyMap() : type.get().getParameters(annotation);
   }

   private static Optional<? extends ValidationResultType> calculateType(ConstraintViolation<?> violation) {
      return ConstraintViolationType.getForAnnotation(violation.getConstraintDescriptor().getAnnotation());
   }

   private static String calculateSourcePointer(Path path, boolean originatesFromConstraintViolationException) {
      List<Node> nodes = StreamSupport.<Node>stream(path.spliterator(), false).toList();
      if (originatesFromConstraintViolationException) {
         Preconditions.checkState(nodes.get(1).getName().startsWith("arg"), "Expected at least 2 path arguments where second argument starts with 'arg'.");
         nodes = nodes.subList(2, nodes.size());
      }

      return "/"
         + nodes.stream()
            .map(node -> node.isInIterable() ? getPathSegmentForContainerElement(path, node) : Optional.of(node.getName()))
            .filter(Optional::isPresent)
            .map(Optional::get)
            .collect(Collectors.joining("/"));
   }

   private static Optional<String> getPathSegmentForContainerElement(Path path, Node node) {
      return switch (node.getName()) {
         case "<iterable element>", "<list element>" -> Optional.of(String.valueOf(node.getIndex()));
         case "<map value>" -> Optional.of((String)node.getKey());
         case "<map key>" -> Optional.empty();
         default -> throw new FailureException("Unsupported node name: " + node.getName() + " while processing " + path);
      };
   }
}
