package ru.yandex.qe.dispenser.ws.d;

import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import javax.inject.Inject;
import javax.ws.rs.ServerErrorException;

import com.google.common.base.Stopwatch;
import io.github.resilience4j.core.IntervalFunction;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

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;
import ru.yandex.monlib.metrics.registry.MetricRegistry;
import ru.yandex.qe.dispenser.domain.d.DApi;
import ru.yandex.qe.dispenser.domain.d.DeliveryDestinationRequestDto;
import ru.yandex.qe.dispenser.domain.d.DeliveryDestinationResponseDto;
import ru.yandex.qe.dispenser.domain.d.DeliveryRequestDto;
import ru.yandex.qe.dispenser.domain.d.DeliveryResponseDto;
import ru.yandex.qe.dispenser.domain.d.DeliveryStatusResponseDto;
import ru.yandex.qe.dispenser.domain.d.ProvideRequestDto;
import ru.yandex.qe.dispenser.domain.d.ProvideResponseDto;
import ru.yandex.qe.dispenser.solomon.SolomonHolder;
import ru.yandex.qe.dispenser.ws.TvmDestination;

@Component
public class DApiHelper {

    private static final String ALL = "all";
    private static final String RATE = "http_outgoing.requests_rate";
    private static final String TIME = "http_outgoing.requests_time_millis";
    private static final String ENDPOINT = "endpoint";
    private static final String CODE = "code";
    private static final String CODE_NON_2XX = "non_2xx";
    private static final String DESTINATION = "destination";
    private static final String D_API = "d-api";

    private final DApi dApi;
    private final TvmDestination dTvmDestination;
    private final MetricRegistry registry;
    private final Rate totalRate;
    private final Rate totalNon2xxRate;
    private final Histogram totalDuration;
    private final Retry retry;

    @Inject
    public DApiHelper(DApi dApi, @Qualifier("d-tvm") TvmDestination dTvmDestination, SolomonHolder solomonHolder) {
        this.dApi = dApi;
        this.dTvmDestination = dTvmDestination;
        this.registry = solomonHolder.getRootRegistry();
        this.totalRate = registry.rate(RATE, Labels.of(CODE, ALL, ENDPOINT, ALL, DESTINATION, D_API));
        this.totalNon2xxRate = registry.rate(RATE, Labels.of(CODE, CODE_NON_2XX, ENDPOINT, ALL, DESTINATION, D_API));
        this.totalDuration = registry.histogramRate(TIME, Labels.of(CODE, ALL, ENDPOINT, ALL, DESTINATION, D_API),
                Histograms.exponential(22, 2.0d, 1.0d));
        this.retry = Retry.of("dHttpRetry", RetryConfig.custom()
                        .maxAttempts(3)
                        .intervalFunction(IntervalFunction.ofExponentialRandomBackoff(500, 1.5, 0.5))
                        .retryOnException(e -> e instanceof SocketTimeoutException || e instanceof ConnectException
                                || e instanceof ServerErrorException)
                .build());
    }

    public DeliveryResponseDto deliver(DeliveryRequestDto deliveryRequest) {
        return dTvmDestination.runAuthorized(() -> measure(() -> dApi.deliver(deliveryRequest)));
    }

    public ProvideResponseDto provide(ProvideRequestDto provideRequest) {
        return dTvmDestination.runAuthorized(() -> measure(() -> dApi.provide(provideRequest)));
    }

    public DeliveryDestinationResponseDto findDestinations(String lang, DeliveryDestinationRequestDto request) {
        return retry.executeSupplier(() -> dTvmDestination
                .runAuthorized(() -> measure(() -> dApi.findDestinations(lang, request))));
    }

    public DeliveryStatusResponseDto getStatuses(String lang, List<String> deliveryIds) {
        return retry.executeSupplier(() -> dTvmDestination
                .runAuthorized(() -> measure(() -> dApi.getStatuses(lang, deliveryIds))));
    }

    private <T> T measure(Supplier<T> supplier) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        totalRate.inc();
        boolean success = false;
        try {
            T result = supplier.get();
            success = true;
            return result;
        } finally {
            if (!success) {
                totalNon2xxRate.inc();
            }
            stopwatch.stop();
            long elapsedMillis = stopwatch.elapsed(TimeUnit.MILLISECONDS);
            totalDuration.record(elapsedMillis);
        }
    }

}
