/*
 * Copyright 2015 Hewlett-Packard Development Company, L.P.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
 */

package com.hp.autonomy.frontend.find.idol.metrics;

import com.autonomy.aci.client.transport.AciParameter;
import com.autonomy.aci.client.transport.AciServerDetails;
import com.hp.autonomy.searchcomponents.idol.annotations.IdolService;
import com.hp.autonomy.searchcomponents.idol.exceptions.AciErrorExceptionAspect;
import com.hp.autonomy.types.requests.idol.actions.params.ActionParams;
import com.hp.autonomy.types.requests.idol.actions.query.params.QueryParams;
import com.hp.autonomy.types.requests.idol.actions.user.UserActions;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.metrics.GaugeService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.hp.autonomy.frontend.find.core.metrics.MetricsConfiguration.*;

/**
 * Default implementation of {@link AciErrorExceptionAspect}
 */
@SuppressWarnings("ProhibitedExceptionDeclared")
@Aspect
@Component
@ConditionalOnProperty(FIND_METRICS_ENABLED_PROPERTY_KEY)
class PerformanceMonitoringAspect {
    static final String SERVICE_METRIC_NAME_PREFIX = METRIC_NAME_SEPARATOR + "service" + METRIC_NAME_SEPARATOR;
    static final String IDOL_REQUEST_METRIC_NAME_PREFIX = METRIC_NAME_SEPARATOR + "idol" + METRIC_NAME_SEPARATOR;
    static final char CLASS_METHOD_SEPARATOR = ':';
    static final String PARAMETER_SEPARATOR = "&";
    static final char NAME_VALUE_SEPARATOR = '=';

    private static final Pattern ILLEGAL_COMPONENT_CHARACTERS = Pattern.compile("\\.");
    private static final Pattern ILLEGAL_CHARACTERS = Pattern.compile("[/\\\\]");
    private static final String ILLEGAL_CHARACTER_REPLACEMENT = "_";

    private final GaugeService gaugeService;
    private final String metricType;

    @Autowired
    public PerformanceMonitoringAspect(final GaugeService gaugeService,
                                       @Value(FIND_METRICS_TYPE_PROPERTY) final String metricType) {
        this.gaugeService = gaugeService;
        this.metricType = metricType;
    }

    @Around("@within(idolService)")
    public Object monitorServiceMethodPerformance(final ProceedingJoinPoint joinPoint, final IdolService idolService) throws Throwable {
        final String metricName = metricType
                + SERVICE_METRIC_NAME_PREFIX
                + sanitiseMetricNameComponent(joinPoint.getSignature().getDeclaringTypeName())
                + CLASS_METHOD_SEPARATOR
                + sanitiseMetricNameComponent(joinPoint.getSignature().getName());
        return monitorMethodPerformance(joinPoint, sanitiseMetricName(metricName));
    }

    @Around(value = "execution(* com.autonomy.aci.client.transport.AciHttpClient.executeAction(..)) && args(serverDetails, parameters)",
            argNames = "joinPoint,serverDetails,parameters")
    public Object monitorIdolRequestPerformance(
            final ProceedingJoinPoint joinPoint,
            final AciServerDetails serverDetails,
            final Collection<? extends AciParameter> parameters) throws Throwable {
        final StringBuilder metricNameBuilder = new StringBuilder(metricType)
                .append(IDOL_REQUEST_METRIC_NAME_PREFIX)
                .append(sanitiseMetricNameComponent(serverDetails.getHost()))
                .append(METRIC_NAME_SEPARATOR)
                .append(serverDetails.getPort())
                .append(METRIC_NAME_SEPARATOR);
        final Map<String, String> parameterMap = parameters.stream()
                .filter(parameter -> !QueryParams.SecurityInfo.name().equalsIgnoreCase(parameter.getName()) && StringUtils.isNotEmpty(parameter.getValue()))
                .collect(Collectors.toMap(AciParameter::getName, AciParameter::getValue));
        final Collection<String> parameterKeyValueStrings = parameterMap.entrySet().stream()
                .map(e -> e.getKey() + NAME_VALUE_SEPARATOR + sanitiseMetricNameComponent(tweakParameterValueInMetricName(e.getKey(), e.getValue())))
                .sorted()
                .collect(Collectors.toList());
        metricNameBuilder.append(String.join(PARAMETER_SEPARATOR, parameterKeyValueStrings));

        return Arrays.stream(UserActions.values()).anyMatch(a ->
                a.name().equalsIgnoreCase(parameterMap.get(ActionParams.Action.name())))
                ? joinPoint.proceed()
                : monitorMethodPerformance(joinPoint, sanitiseMetricName(metricNameBuilder.toString()));
    }

    private Object monitorMethodPerformance(final ProceedingJoinPoint joinPoint, final String metricName) throws Throwable {
        final StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        try {
            return joinPoint.proceed();
        } finally {
            stopWatch.stop();
            gaugeService.submit(metricName, stopWatch.getTotalTimeMillis());
        }
    }

    private String sanitiseMetricName(final CharSequence metricNameBuilder) {
        return ILLEGAL_CHARACTERS.matcher(metricNameBuilder).replaceAll(ILLEGAL_CHARACTER_REPLACEMENT);
    }

    private String sanitiseMetricNameComponent(final CharSequence metricNameBuilder) {
        return ILLEGAL_COMPONENT_CHARACTERS.matcher(metricNameBuilder).replaceAll(ILLEGAL_CHARACTER_REPLACEMENT);
    }

    private CharSequence tweakParameterValueInMetricName(final String name, final CharSequence value) {
        return QueryParams.Text.name().equalsIgnoreCase(name) && !"*".equals(value) ? "[any]" : value;
    }
}

@@ -636,32 +636,120 @@
 ms.QueryParams;%0A
+import com.hp.autonomy.types.requests.idol.actions.tags.params.GetQueryTagValuesParams;%0A
 import com.hp.au
@@ -2409,16 +2409,77 @@
 T = %22_%22;
+%0A    private static final String VALUE_PLACEHOLDER = %22%5Bany%5D%22;
 %0A%0A    pr
@@ -6214,17 +6214,123 @@
 ue) 
-? %22%5Bany%5D%22
+%7C%7C GetQueryTagValuesParams.Ranges.name().equalsIgnoreCase(name)%0A                ? VALUE_PLACEHOLDER%0A               
  : v

Remove complex range information from stats sent to Graphite