package com.airlock.iam.admin.infrastructure.mapper.prot;

import ch.ergon.restal.jsonapi.parameter.FilterParameter;
import ch.ergon.restal.jsonapi.parameter.PageParameter;
import ch.ergon.restal.jsonapi.parameter.SortParameter;
import ch.ergon.restal.jsonapi.parameter.FilterParameter.Condition;
import ch.ergon.restal.jsonapi.parameter.FilterParameter.Disjunction;
import ch.ergon.restal.jsonapi.parameter.FilterParameter.Operator;
import ch.ergon.restal.jsonapi.parameter.SortParameter.Criterion;
import com.airlock.iam.techclient.admin.domain.model.PlainTechClient;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.commons.lang3.Strings;

public class TechClientSearch {
   private final FilterParameter filterParameter;
   private final PageParameter pageParameter;
   private final SortParameter sortParameter;

   public TechClientSearch.TechClientSearchResult search(Collection<PlainTechClient> clients, int pageLimit) {
      AtomicLong totalMatchingClients = new AtomicLong();
      Collection<PlainTechClient> pagedClients = clients.stream()
         .filter(techClientFilter(this.filterParameter))
         .peek(t -> totalMatchingClients.incrementAndGet())
         .sorted(techClientComparator(this.sortParameter))
         .skip(this.pageParameter.getOffset())
         .limit(pageLimit)
         .collect(Collectors.toList());
      return new TechClientSearch.TechClientSearchResult(pagedClients, totalMatchingClients.intValue());
   }

   private static Predicate<PlainTechClient> techClientFilter(FilterParameter filterParameter) {
      return client -> {
         List<Disjunction> filters = filterParameter.parse();
         return filters.isEmpty()
            ? true
            : filters.stream().map(Disjunction::getConditions).flatMap(Collection::stream).anyMatch(matchTechClientAttributes(client));
      };
   }

   private static Predicate<Condition> matchTechClientAttributes(PlainTechClient client) {
      return condition -> {
         String property = condition.getProperty();
         if ("displayName".equalsIgnoreCase(property)) {
            return condition.getOperator() == Operator.EQUALS
               ? client.getDisplayName().equalsIgnoreCase(condition.getValue())
               : Strings.CI.contains(client.getDisplayName(), condition.getValue());
         } else if ("id".equalsIgnoreCase(property)) {
            return condition.getOperator() == Operator.EQUALS
               ? client.getId().asString().equalsIgnoreCase(condition.getValue())
               : Strings.CI.contains(client.getId().asString(), condition.getValue());
         } else if ("label".equalsIgnoreCase(property)) {
            return condition.getOperator() == Operator.EQUALS
               ? client.getLabel().map(l -> l.equalsIgnoreCase(condition.getValue())).orElse(false)
               : client.getLabel().map(l -> Strings.CI.contains(l, condition.getValue())).orElse(false);
         } else {
            return false;
         }
      };
   }

   private static Comparator<PlainTechClient> techClientComparator(SortParameter sortParameter) {
      Comparator<PlainTechClient> comparator = Comparator.comparing(PlainTechClient::getDisplayName, String.CASE_INSENSITIVE_ORDER);
      List<Criterion> criteria = sortParameter.parse();
      if (criteria.isEmpty()) {
         return comparator;
      }

      Criterion criterion = criteria.getFirst();
      if ("displayName".equals(criterion.getName())) {
         comparator = Comparator.comparing(PlainTechClient::getDisplayName, String.CASE_INSENSITIVE_ORDER);
      } else if ("id".equals(criterion.getName())) {
         comparator = Comparator.comparing(c -> c.getId().asString(), String.CASE_INSENSITIVE_ORDER);
      } else if ("creationDate".equals(criterion.getName())) {
         comparator = Comparator.comparing(PlainTechClient::getCreationDate);
      } else if ("label".equals(criterion.getName())) {
         comparator = Comparator.comparing(c -> c.getLabel().orElse(""), String.CASE_INSENSITIVE_ORDER);
      } else if ("locked".equals(criterion.getName())) {
         comparator = Comparator.comparing(c -> c.getLockInfo().isPresent());
      }

      return criterion.isAscending() ? comparator : comparator.reversed();
   }

   @SuppressFBWarnings(justification = "generated code")
   public TechClientSearch(FilterParameter filterParameter, PageParameter pageParameter, SortParameter sortParameter) {
      this.filterParameter = filterParameter;
      this.pageParameter = pageParameter;
      this.sortParameter = sortParameter;
   }

   public static final class TechClientSearchResult {
      private final Collection<PlainTechClient> matchingClients;
      private final int totalCount;

      @SuppressFBWarnings(justification = "generated code")
      public TechClientSearchResult(Collection<PlainTechClient> matchingClients, int totalCount) {
         this.matchingClients = matchingClients;
         this.totalCount = totalCount;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Collection<PlainTechClient> getMatchingClients() {
         return this.matchingClients;
      }

      @SuppressFBWarnings(justification = "generated code")
      public int getTotalCount() {
         return this.totalCount;
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public boolean equals(Object o) {
         if (o == this) {
            return true;
         } else if (!(o instanceof TechClientSearch.TechClientSearchResult other)) {
            return false;
         } else {
            if (this.totalCount != other.totalCount) {
               return false;
            }

            Object this$matchingClients = this.matchingClients;
            Object other$matchingClients = other.matchingClients;
            return this$matchingClients == null ? other$matchingClients == null : this$matchingClients.equals(other$matchingClients);
         }
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public int hashCode() {
         int PRIME = 59;
         int result = 1;
         result = result * 59 + this.totalCount;
         Object $matchingClients = this.matchingClients;
         return result * 59 + ($matchingClients == null ? 43 : $matchingClients.hashCode());
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "TechClientSearch.TechClientSearchResult(matchingClients=" + this.getMatchingClients() + ", totalCount=" + this.getTotalCount() + ")";
      }
   }
}
