/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the "Elastic License
 * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
 * Public License v 1"; you may not use this file except in compliance with, at
 * your election, the "Elastic License 2.0", the "GNU Affero General Public
 * License v3.0 only", or the "Server Side Public License, v 1".
 */

package org.elasticsearch.health.node;

import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.metadata.ProjectMetadata;
import org.elasticsearch.cluster.node.DiscoveryNode;

import java.util.Collection;
import java.util.Comparator;
import java.util.Locale;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.joining;

/**
 * This class provides helper methods to construct display messages for the health indicators. There are a lot of
 * messages generated by the health indicators namely, symptoms, impacts and diagnoses. The aim of this is to
 * make the code less error-prone and more readable.
 */
public class HealthIndicatorDisplayValues {

    /**
     * Formats the display name of a discovery node in the following way:
     * - [id][name] or
     * - [id] if name is null
     */
    public static String getNodeName(DiscoveryNode node) {
        if (node.getName() != null) {
            return String.format(Locale.ROOT, "[%s][%s]", node.getId(), node.getName());
        }
        return String.format(Locale.ROOT, "[%s]", node.getId());
    }

    /**
     * Creates a string that displays max 10 indices from the given set to be used as examples in
     * logging or user messages. The indices are sorted by priority and then by name to ensure a
     * deterministic message. If there are more indices than 10, it adds the '...' suffix.
     */
    @Deprecated
    public static String getTruncatedIndices(Set<String> indices, Metadata clusterMetadata) {
        final int maxIndices = 10;
        String truncatedIndicesString = indices.stream()
            .sorted(indicesComparatorByPriorityAndName(clusterMetadata))
            .limit(maxIndices)
            .collect(joining(", "));
        if (maxIndices < indices.size()) {
            truncatedIndicesString = truncatedIndicesString + ", ...";
        }
        return truncatedIndicesString;
    }

    /**
     * Creates a string that displays max 10 indices from the given set to be used as examples in
     * logging or user messages. The indices are sorted by priority and then by name to ensure a
     * deterministic message. If there are more indices than 10, it adds the '...' suffix.
     */
    public static String getTruncatedProjectIndices(
        Set<ProjectIndexName> indices,
        Metadata clusterMetadata,
        boolean supportsMultipleProjects
    ) {
        final int maxIndices = 10;
        String truncatedIndicesString = indices.stream()
            .sorted(indicesComparatorByPriorityAndProjectIndex(clusterMetadata, supportsMultipleProjects))
            .limit(maxIndices)
            .map(projectIndexName -> projectIndexName.toString(supportsMultipleProjects))
            .collect(joining(", "));
        if (maxIndices < indices.size()) {
            truncatedIndicesString = truncatedIndicesString + ", ...";
        }
        return truncatedIndicesString;
    }

    /**
     * Creates a string that displays all the values that fulfilled the predicate sorted in the natural order.
     * @param values, the values to be displayed
     * @param predicate, the predicated by which all values will be filtered
     * @param toString, the desired way to convert the type 'T' to string for the purpose of this message.
     */
    public static <T> String getSortedUniqueValuesString(Collection<T> values, Predicate<T> predicate, Function<T, String> toString) {
        return values.stream().filter(predicate).map(toString).distinct().sorted().collect(Collectors.joining(", "));
    }

    /**
     * Creates a string that displays all the values sorted in the natural order.
     * @param values, the values to be displayed
     * @param toString, the desired way to convert the type 'T' to string for the purpose of this message.
     */
    public static <T> String getSortedUniqueValuesString(Collection<T> values, Function<T, String> toString) {
        return values.stream().map(toString).distinct().sorted().collect(Collectors.joining(", "));
    }

    /**
     * Provides the correct form (singular or plural) of the word index depending on the given count.
     */
    public static String indices(int count) {
        return count == 1 ? "index" : "indices";
    }

    /**
     * Provides the correct form (singular or plural) of the verb to be depending on the given count.
     */
    public static String are(int count) {
        return count == 1 ? "is" : "are";
    }

    /**
     * Provides the correct form (singular or plural) of the word this depending on the given count.
     */
    public static String these(int count) {
        return count == 1 ? "this" : "these";
    }

    /**
     * Provides the correct form (singular or plural) of a regular noun depending on the given count.
     */
    public static String regularNoun(String noun, int count) {
        return count == 1 ? noun : noun + "s";
    }

    /**
     * Provides the correct form (singular or plural) of a regular verb depending on the given count.
     */
    public static String regularVerb(String verb, int count) {
        return count == 1 ? verb + "s" : verb;
    }

    /**
     * Sorts index names by their priority first, then alphabetically by name. If the priority cannot be determined for an index then
     * a priority of -1 is used to sort it behind other index names.
     * @param clusterMetadata Used to look up index priority.
     * @return Comparator instance
     */
    @Deprecated
    public static Comparator<String> indicesComparatorByPriorityAndName(Metadata clusterMetadata) {
        // We want to show indices with a numerically higher index.priority first (since lower priority ones might get truncated):
        return Comparator.comparingInt((String indexName) -> {
            IndexMetadata indexMetadata = clusterMetadata.getProject().index(indexName);
            return indexMetadata == null ? -1 : indexMetadata.priority();
        }).reversed().thenComparing(Comparator.naturalOrder());
    }

    /**
     * Sorts index names by their priority first, then alphabetically by name. If the priority cannot be determined for an index then
     * a priority of -1 is used to sort it behind other index names.
     * @param clusterMetadata Used to look up index priority.
     * @param supportsMultipleProjects Whether cluster supports multi-project
     * @return Comparator instance
     */
    public static Comparator<ProjectIndexName> indicesComparatorByPriorityAndProjectIndex(
        Metadata clusterMetadata,
        boolean supportsMultipleProjects
    ) {
        // We want to show indices with a numerically higher index.priority first (since lower priority ones might get truncated):
        return Comparator.comparingInt((ProjectIndexName projectIndexName) -> {
            ProjectMetadata projectMetadata = clusterMetadata.getProject(projectIndexName.projectId());
            IndexMetadata indexMetadata = projectMetadata.index(projectIndexName.indexName());
            return indexMetadata == null ? -1 : indexMetadata.priority();
        }).reversed().thenComparing(projectIndex -> projectIndex.toString(supportsMultipleProjects));
    }
}
