package com.lanyuanxiaoyao.service.sync.metrics;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.lanyuanxiaoyao.service.common.Constants;
import com.lanyuanxiaoyao.service.sync.configuration.GlobalConfiguration;
import com.lanyuanxiaoyao.service.sync.utils.JacksonUtils;
import dev.failsafe.Failsafe;
import dev.failsafe.RetryPolicy;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.LongAdder;
import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 指标基础类
 *
 * @author ZhangJiacheng
 * @date 2022-06-13
 */
public abstract class AbstractMetric implements Metric {
    private static final Logger logger = LoggerFactory.getLogger(AbstractMetric.class);
    private static final ObjectMapper MAPPER = JacksonUtils.getMapper();
    private static final RetryPolicy<HttpResponse> PUBLISH_RETRY = RetryPolicy.<HttpResponse>builder()
            .handle(Exception.class)
            .withDelay(Duration.ofSeconds(1))
            .withMaxAttempts(5)
            .build();
    private final GlobalConfiguration globalConfiguration;
    private final List<String> lineCache = ListUtil.toList();
    private final LongAdder autoPublishCount = new LongAdder();
    private List<HttpMetricsRequest> requests = new ArrayList<>();

    public AbstractMetric(GlobalConfiguration globalConfiguration) {
        this.globalConfiguration = globalConfiguration;
    }

    public void setRequests(HttpMetricsRequest... requests) {
        setRequests(ListUtil.toList(requests));
    }

    public void setRequests(List<HttpMetricsRequest> requests) {
        this.requests = requests;
    }

    public void addRequest(HttpMetricsRequest request) {
        this.requests.add(request);
    }

    public void addRequests(HttpMetricsRequest... requests) {
        addRequests(ListUtil.toList(requests));
    }

    public void addRequests(List<HttpMetricsRequest> requests) {
        this.requests.addAll(requests);
    }

    @Override
    public void addTag(String key, String value) {
        requests.forEach(request -> request.addTag(key, value));
    }

    @Override
    public void makePoint(boolean autoPublish, int batch) {
        if (!globalConfiguration.getMetricEnable()) {
            return;
        }
        if (autoPublish) {
            if (autoPublishCount.sum() >= batch) {
                publish();
            }
            autoPublishCount.increment();
        }
        makePoint();
    }

    public synchronized void publish() {
        if (!globalConfiguration.getMetricEnable()) {
            return;
        }
        try {
            requests.stream()
                    .filter(request -> !request.isEmpty())
                    .map(request -> {
                        try {
                            String data = MAPPER.writeValueAsString(request);
                            request.clear();
                            return data;
                        } catch (JsonProcessingException e) {
                            logger.warn("Parse metrics failure: " + request, e);
                        }
                        return null;
                    })
                    .filter(Objects::nonNull)
                    .forEach(lineCache::add);
            if (lineCache.isEmpty()) {
                return;
            }
            String lines = String.join("\n", lineCache);
            logger.debug("Push metrics: \n{}", lines);
            HttpResponse response = Failsafe.with(PUBLISH_RETRY)
                    .get(() -> HttpUtil.createPost(globalConfiguration.getMetricPublishUrl())
                            .body(lines)
                            .basicAuth(Constants.VICTORIA_USERNAME, Constants.VICTORIA_PASSWORD)
                            .timeout(globalConfiguration.getMetricPublishTimeout())
                            .execute());
            if (response.isOk()) {
                logger.debug("Metrics push success");
            }
        } catch (Throwable throwable) {
            logger.warn("Push metrics failure, url: " + globalConfiguration.getMetricPublishUrl(), throwable);
        } finally {
            lineCache.clear();
            autoPublishCount.reset();
        }
    }
}
