package ru.yandex.webmaster3.core.solomon;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.apache.commons.lang3.tuple.Pair;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import ru.yandex.webmaster3.core.solomon.metric.SolomonCommonLabels;
import ru.yandex.webmaster3.core.util.RetryUtils;

/**
 * Created by ifilippov5 on 05.05.17.
 */
public class HandleCommonMetricsService {
    private static final Logger log = LoggerFactory.getLogger(HandleCommonMetricsService.class);
    private static final int QUEUE_CAPACITY = 1000;
    // при достижении какого размера будем отправлять пачку в Solomon
    private static final int SEND_DATA_SIZE_THRESHOLD = 1024 * 1024;

    private String project;
    private String cluster;
    private String service;
    private String host;
    private SolomonPushAPIService solomonPushAPIService;

    private ExecutorService executor;
    private BlockingQueue<Pair<List<SolomonSensor>, Integer>> sensorsQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);


    @Autowired
    public HandleCommonMetricsService(String project,
                                      String cluster,
                                      String service,
                                      String host,
                                      SolomonPushAPIService solomonPushAPIService) {

        this.project = project;
        this.cluster = cluster;
        this.service = service;
        this.host = host;
        this.solomonPushAPIService = solomonPushAPIService;
    }

    /***
     *  while (!Thread.interrupted()) возможно стоит заменить на @Scheduled и по расписанию обрабатывать только один блок данных
     */
    public void init() {
        ThreadFactory threadFactory = new ThreadFactoryBuilder()
                .setDaemon(true)
                .setNameFormat("solomon-pusher-%d")
                .build();
        executor = Executors.newSingleThreadExecutor(threadFactory);
        executor.execute(() -> {
            try {
                while (!Thread.interrupted()) {
                    // ждем свежих данных
                    Pair<List<SolomonSensor>, Integer> pair = sensorsQueue.take();
                    List<SolomonSensor> allSensors = new ArrayList<>(pair.getKey());
                    int totalSize = pair.getRight();
                    while (!sensorsQueue.isEmpty() && totalSize < SEND_DATA_SIZE_THRESHOLD) {
                        pair = sensorsQueue.poll();
                        totalSize += pair.getRight();
                        allSensors.addAll(pair.getLeft());
                    }
                    log.debug("Sensors list size: {}, size in bytes: {}", allSensors.size(), totalSize);
                    try {
                        boolean sent = RetryUtils.query(
                                RetryUtils.linearBackoff(5, Duration.standardMinutes(5)),
                                () -> solomonPushAPIService.pushMetrics(allSensors, new SolomonCommonLabels(project, service, cluster, host))
                        );
                        if (!sent) {
                            log.error("Solomon write failed unrecoverably - will drop " + totalSize + " bytes.");
                        }
                    } catch (Exception e) {
                        log.error("Solomon write failed - will drop " + totalSize + " bytes.", e);
                    }
                }
            } catch (InterruptedException e) {
                log.error("Sensors pusher was interrupted", e);
            }
        });
    }

    public void destroy() {
        executor.shutdownNow();
        try {
            executor.awaitTermination(1, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            // Ignore
        }
    }

    public void handle(List<SolomonSensor> sensors, int size) {
        if (!sensorsQueue.offer(Pair.of(sensors, size))) {
            log.error("Sensors queue is full, will try to send synchronously");
            try {
                if (!solomonPushAPIService.pushMetrics(sensors, new SolomonCommonLabels(project, service, cluster, host))) {
                    log.error("Synchronous metrics push failed unrecoverably");
                }
            } catch (Exception e) {
                log.error("Synchronous metrics push failed", e);
            }
        }
    }
}
