package com.cameleer.server.app.metrics;

import com.cameleer.server.core.storage.ServerMetricsStore;
import com.cameleer.server.core.storage.model.ServerMetricSample;
import io.micrometer.core.instrument.Measurement;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Periodically snapshots every meter in the server's {@link MeterRegistry}
 * and writes the result to ClickHouse via {@link ServerMetricsStore}. This
 * gives us historical server-health data (buffer depths, agent transitions,
 * flush latency, JVM memory, HTTP response counts, etc.) without requiring
 * an external Prometheus.
 *
 * <p>Each Micrometer {@link Meter#measure() measurement} becomes one row, so
 * a single Timer produces rows for {@code count}, {@code total_time}, and
 * {@code max} each tick. Counter values are cumulative since meter
 * registration (Prometheus convention) — callers compute rate() themselves.
 *
 * <p>Disabled via {@code cameleer.server.self-metrics.enabled=false}.
 */
@Component
@ConditionalOnProperty(
        prefix = "cameleer.server.self-metrics",
        name = "enabled",
        havingValue = "true",
        matchIfMissing = true)
public class ServerMetricsSnapshotScheduler {

    private static final Logger log = LoggerFactory.getLogger(ServerMetricsSnapshotScheduler.class);

    private final MeterRegistry registry;
    private final ServerMetricsStore store;
    private final String tenantId;
    private final String serverInstanceId;

    public ServerMetricsSnapshotScheduler(
            MeterRegistry registry,
            ServerMetricsStore store,
            @Value("${cameleer.server.tenant.id:default}") String tenantId,
            @Qualifier("serverInstanceId") String serverInstanceId) {
        this.registry = registry;
        this.store = store;
        this.tenantId = tenantId;
        this.serverInstanceId = serverInstanceId;
    }

    @Scheduled(fixedDelayString = "${cameleer.server.self-metrics.interval-ms:60000}",
               initialDelayString = "${cameleer.server.self-metrics.interval-ms:60000}")
    public void snapshot() {
        try {
            Instant now = Instant.now();
            List<ServerMetricSample> batch = new ArrayList<>();

            for (Meter meter : registry.getMeters()) {
                Meter.Id id = meter.getId();
                Map<String, String> tags = flattenTags(id.getTagsAsIterable());
                String type = id.getType().name().toLowerCase();

                for (Measurement m : meter.measure()) {
                    double v = m.getValue();
                    if (!Double.isFinite(v)) continue;
                    batch.add(new ServerMetricSample(
                            tenantId,
                            now,
                            serverInstanceId,
                            id.getName(),
                            type,
                            m.getStatistic().getTagValueRepresentation(),
                            v,
                            tags));
                }
            }

            if (!batch.isEmpty()) {
                store.insertBatch(batch);
                log.debug("Persisted {} server self-metric samples", batch.size());
            }
        } catch (Exception e) {
            log.warn("Server self-metrics snapshot failed: {}", e.getMessage());
        }
    }

    private static Map<String, String> flattenTags(Iterable<Tag> tags) {
        Map<String, String> out = new LinkedHashMap<>();
        for (Tag t : tags) {
            out.put(t.getKey(), t.getValue());
        }
        return out;
    }
}
