package ru.yandex.webmaster3.monitoring.queue.checkurl;

import com.google.common.base.Stopwatch;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.commons.lang3.mutable.MutableLong;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.scheduling.annotation.Scheduled;
import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.solomon.HandleCommonMetricsService;
import ru.yandex.webmaster3.core.solomon.Indicators;
import ru.yandex.webmaster3.core.solomon.SolomonSensor;
import ru.yandex.webmaster3.monitoring.common.YtQueueUtils;
import ru.yandex.webmaster3.storage.util.ydb.exception.WebmasterYdbException;
import ru.yandex.webmaster3.storage.url.checker2.dao.UrlCheckYtRequestsYDao;
import ru.yandex.webmaster3.storage.util.ydb.exception.WebmasterYdbException;
import ru.yandex.webmaster3.storage.util.yt.YtPath;
import ru.yandex.webmaster3.storage.util.yt.YtService;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Мониторинг всего для сheck url
 */
public class UrlCheckMonitoringService {
    private static final Logger log = LoggerFactory.getLogger(UrlCheckMonitoringService.class);

    private static final String SECTION_LABEL_VALUE = "checkurl";
    private static final String CASSANDRA_QUEUE_TYPE = "cassandra_queue";
    private static final String CHECKURL_QUEUE_TYPE = "checkurl_queue";
    private static final String YT_QUEUE_TYPE = "yt_queue";
    private static final String YT_RESULTS_QUEUE_TYPE = "yt_results";
    private static final int AVERAGE_SENSORS_SIZE = 500;

    private HandleCommonMetricsService handleCommonMetricsService;
    private YtService ytService;
    private UrlCheckYtRequestsYDao urlCheckYtRequestsYDao;

    private boolean enabled;
    private long refreshIntervalSeconds;
    private YtPath ytRequestsPath;
    private YtPath ytResultsPath;

    @Scheduled(cron = "${webmaster3.monitoring.solomon.checkurl.refresh-cron}")
    private void push() throws Exception {
        if (!enabled) {
            log.warn("Checkurl push metrics service disabled");
            return;
        }
        log.info("Started collecting metrics for checkurl");
        List<SolomonSensor> sensors = new ArrayList<>();

        Stopwatch sw = Stopwatch.createStarted();
        Pair<Integer, Long> unprocessedCassandraRequestsStats = getUnprocessedCassandraRequestsStats();
        sensors.add(SolomonSensor.createAligned(refreshIntervalSeconds, unprocessedCassandraRequestsStats.getLeft())
                .withLabel(SolomonSensor.LABEL_SECTION, SECTION_LABEL_VALUE)
                .withLabel(SolomonSensor.LABEL_DATA_TYPE, CASSANDRA_QUEUE_TYPE)
                .withLabel(SolomonSensor.LABEL_INDICATOR, Indicators.QUEUE_SIZE));
        sensors.add(SolomonSensor.createAligned(refreshIntervalSeconds, unprocessedCassandraRequestsStats.getRight() / 1000L)
                .withLabel(SolomonSensor.LABEL_SECTION, SECTION_LABEL_VALUE)
                .withLabel(SolomonSensor.LABEL_DATA_TYPE, CASSANDRA_QUEUE_TYPE)
                .withLabel(SolomonSensor.LABEL_INDICATOR, Indicators.DATA_AGE));
        log.info("Collected stats for cassandra requests in {}s", sw.stop().elapsed(TimeUnit.SECONDS));

        sw.reset().start();
        YtQueueUtils.QueueStats unprocessedYtRequestsStats = YtQueueUtils.getQueueStats(
            ytService, ytRequestsPath, YtQueueUtils.ALL_TABLES_FILTER, YtQueueUtils.JAVA_TIMESTAMP_EXTRACTOR);
        sensors.add(SolomonSensor.createAligned(refreshIntervalSeconds, unprocessedYtRequestsStats.tablesCount)
                .withLabel(SolomonSensor.LABEL_SECTION, SECTION_LABEL_VALUE)
                .withLabel(SolomonSensor.LABEL_DATA_TYPE, YT_QUEUE_TYPE)
                .withLabel(SolomonSensor.LABEL_INDICATOR, Indicators.QUEUE_SIZE));
        sensors.add(SolomonSensor.createAligned(refreshIntervalSeconds, unprocessedYtRequestsStats.maxAgeInMillis / 1000)
                .withLabel(SolomonSensor.LABEL_SECTION, SECTION_LABEL_VALUE)
                .withLabel(SolomonSensor.LABEL_DATA_TYPE, YT_QUEUE_TYPE)
                .withLabel(SolomonSensor.LABEL_INDICATOR, Indicators.DATA_AGE));
        log.info("Collected stats for unprocessed YT requests in {}s", sw.stop().elapsed(TimeUnit.SECONDS));

        sw.reset().start();
        YtQueueUtils.QueueStats unprocessedYtResultsStats = YtQueueUtils.getQueueStats(
            ytService, ytResultsPath, YtQueueUtils.ALL_TABLES_FILTER, YtQueueUtils.JAVA_TIMESTAMP_EXTRACTOR);
        sensors.add(SolomonSensor.createAligned(refreshIntervalSeconds, unprocessedYtResultsStats.tablesCount)
                .withLabel(SolomonSensor.LABEL_SECTION, SECTION_LABEL_VALUE)
                .withLabel(SolomonSensor.LABEL_DATA_TYPE, YT_RESULTS_QUEUE_TYPE)
                .withLabel(SolomonSensor.LABEL_INDICATOR, Indicators.QUEUE_SIZE));
        sensors.add(SolomonSensor.createAligned(refreshIntervalSeconds, unprocessedYtResultsStats.maxAgeInMillis / 1000)
                .withLabel(SolomonSensor.LABEL_SECTION, SECTION_LABEL_VALUE)
                .withLabel(SolomonSensor.LABEL_DATA_TYPE, YT_RESULTS_QUEUE_TYPE)
                .withLabel(SolomonSensor.LABEL_INDICATOR, Indicators.DATA_AGE));
        log.info("Collected stats for unprocessed YT results in {}s", sw.stop().elapsed(TimeUnit.SECONDS));

        handleCommonMetricsService.handle(sensors, AVERAGE_SENSORS_SIZE);
        log.info("Done collecting metrics for checkurl");
    }

    private void rethow(WebmasterYdbException e) {
        throw new WebmasterException("Trouble interacting with Cassandra",
                new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
    }

    /**
     * Возвращает пару (размер очереди, возраст) для очереди запросов в Кассандре,
     * ожидающих отправку в Yt.
     */
    private Pair<Integer, Long> getUnprocessedCassandraRequestsStats() {
        try {
            long now = System.currentTimeMillis();
            MutableInt count = new MutableInt(0);
            MutableLong minTime = new MutableLong(Long.MAX_VALUE);
            urlCheckYtRequestsYDao.foreachRequest(
                    req -> {
                        long reqTime = req.getCreatedTime().getMillis();
                        if (minTime.getValue() > reqTime) {
                            minTime.setValue(reqTime);
                        }
                        count.increment();
                    }
            );
            if (minTime.getValue() == Long.MAX_VALUE) {
                minTime.setValue(now);
            }
            return Pair.of(count.getValue(), now - minTime.getValue());
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Failed to obtain list of requests to process",
                    new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
        }
    }

    @Required
    public void setHandleCommonMetricsService(HandleCommonMetricsService handleCommonMetricsService) {
        this.handleCommonMetricsService = handleCommonMetricsService;
    }

    @Required
    public void setYtService(YtService ytService) {
        this.ytService = ytService;
    }

    @Required
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    @Required
    public void setRefreshIntervalSeconds(long refreshIntervalSeconds) {
        this.refreshIntervalSeconds = refreshIntervalSeconds;
    }

    @Required
    public void setYtRequestsPath(YtPath ytRequestsPath) {
        this.ytRequestsPath = ytRequestsPath;
    }

    @Required
    public void setYtResultsPath(YtPath ytResultsPath) {
        this.ytResultsPath = ytResultsPath;
    }

    @Required
    public void setUrlCheckYtRequestsYDao(UrlCheckYtRequestsYDao urlCheckYtRequestsYDao) {
        this.urlCheckYtRequestsYDao = urlCheckYtRequestsYDao;
    }
}
