package ru.yandex.qe.dispenser;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import javax.inject.Inject;

import com.google.common.base.Stopwatch;

import ru.yandex.qe.dispenser.solomon.SolomonHolder;
import ru.yandex.qe.hitman.tvm.qloud.QloudTvmService;
import ru.yandex.qe.hitman.tvm.qloud.TvmServiceTicketInfo;
import ru.yandex.qe.hitman.tvm.qloud.TvmUserTicketInfo;
import ru.yandex.monlib.metrics.histogram.Histograms;
import ru.yandex.monlib.metrics.labels.Labels;
import ru.yandex.monlib.metrics.primitives.Histogram;
import ru.yandex.monlib.metrics.primitives.Rate;

public class InstrumentedQloudTvmService {

    private static final String DURATION = "tvm.request_duration_millis";
    private static final String RATE = "tvm.request_rate";
    private static final String ERROR_RATE = "tvm.error_rate";
    private static final String ENDPOINT = "endpoint";
    private static final String ALL = "all";
    private static final String GET_TICKET = "getTicket";
    private static final String IS_VALID_TARGET = "isValidTarget";
    private static final String VALIDATE_SERVICE_TICKET = "validateServiceTicket";
    private static final String VALIDATE_USER_TICKET = "validateUserTicket";
    private static final List<String> ENDPOINTS = Arrays.asList(ALL, GET_TICKET, IS_VALID_TARGET, VALIDATE_SERVICE_TICKET,
            VALIDATE_USER_TICKET);

    private final QloudTvmService qloudTvmService;
    private final Map<String, Histogram> durationPerEndpoint = new HashMap<>();
    private final Map<String, Rate> totalRatePerEndpoint = new HashMap<>();
    private final Map<String, Rate> errorRatePerEndpoint = new HashMap<>();


    @Inject
    public InstrumentedQloudTvmService(final QloudTvmService qloudTvmService, final SolomonHolder solomonHolder) {
        this.qloudTvmService = qloudTvmService;
        for (String endpoint : ENDPOINTS) {
            this.durationPerEndpoint.put(endpoint, solomonHolder.getRootRegistry().histogramRate(DURATION, Labels.of(ENDPOINT, endpoint),
                    Histograms.exponential(22, 2.0d, 1.0d)));
            this.totalRatePerEndpoint.put(endpoint, solomonHolder.getRootRegistry().rate(RATE, Labels.of(ENDPOINT, endpoint)));
            this.errorRatePerEndpoint.put(endpoint, solomonHolder.getRootRegistry().rate(ERROR_RATE, Labels.of(ENDPOINT, endpoint)));
        }
    }

    public String getTicket(final String targetClientId) {
        return measure(() -> qloudTvmService.getTicket(targetClientId), GET_TICKET);
    }

    public Optional<TvmServiceTicketInfo> validateServiceTicket(final String serviceTicket) {
        return measure(() -> qloudTvmService.validateServiceTicket(serviceTicket), VALIDATE_SERVICE_TICKET);
    }

    public Optional<TvmUserTicketInfo> validateUserTicket(final String userTicket) {
        return measure(() -> qloudTvmService.validateUserTicket(userTicket), VALIDATE_USER_TICKET);
    }

    private <T> T measure(final Supplier<T> supplier, final String endpoint) {
        final Stopwatch stopwatch = Stopwatch.createStarted();
        totalRatePerEndpoint.get(ALL).inc();
        totalRatePerEndpoint.get(endpoint).inc();
        boolean success = false;
        try {
            final T result = supplier.get();
            success = true;
            return result;
        } finally {
            if (!success) {
                errorRatePerEndpoint.get(ALL).inc();
                errorRatePerEndpoint.get(endpoint).inc();
            }
            final long elapsedMillis = stopwatch.elapsed(TimeUnit.MILLISECONDS);
            durationPerEndpoint.get(ALL).record(elapsedMillis);
            durationPerEndpoint.get(endpoint).record(elapsedMillis);
        }
    }

}
