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.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.Test;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import static org.assertj.core.api.Assertions.assertThat;

class ServerMetricsSnapshotSchedulerTest {

    @Test
    void snapshot_capturesCounterGaugeAndTimerMeasurements() {
        MeterRegistry registry = new SimpleMeterRegistry();

        Counter counter = Counter.builder("cameleer.test.counter")
                .tag("env", "dev")
                .register(registry);
        counter.increment(3);

        AtomicInteger gaugeSource = new AtomicInteger(42);
        Gauge.builder("cameleer.test.gauge", gaugeSource, AtomicInteger::doubleValue)
                .register(registry);

        Timer timer = Timer.builder("cameleer.test.timer").register(registry);
        timer.record(Duration.ofMillis(5));
        timer.record(Duration.ofMillis(15));

        RecordingStore store = new RecordingStore();
        ServerMetricsSnapshotScheduler scheduler =
                new ServerMetricsSnapshotScheduler(registry, store, "tenant-7", "server-A");

        scheduler.snapshot();

        assertThat(store.batches).hasSize(1);
        List<ServerMetricSample> samples = store.batches.get(0);

        // Every sample is stamped with tenant + instance + finite value
        assertThat(samples).allSatisfy(s -> {
            assertThat(s.tenantId()).isEqualTo("tenant-7");
            assertThat(s.serverInstanceId()).isEqualTo("server-A");
            assertThat(Double.isFinite(s.value())).isTrue();
            assertThat(s.collectedAt()).isNotNull();
        });

        // Counter -> 1 row with statistic=count, value=3, tag propagated
        List<ServerMetricSample> counterRows = samples.stream()
                .filter(s -> s.metricName().equals("cameleer.test.counter"))
                .toList();
        assertThat(counterRows).hasSize(1);
        assertThat(counterRows.get(0).statistic()).isEqualTo("count");
        assertThat(counterRows.get(0).metricType()).isEqualTo("counter");
        assertThat(counterRows.get(0).value()).isEqualTo(3.0);
        assertThat(counterRows.get(0).tags()).containsEntry("env", "dev");

        // Gauge -> 1 row with statistic=value
        List<ServerMetricSample> gaugeRows = samples.stream()
                .filter(s -> s.metricName().equals("cameleer.test.gauge"))
                .toList();
        assertThat(gaugeRows).hasSize(1);
        assertThat(gaugeRows.get(0).statistic()).isEqualTo("value");
        assertThat(gaugeRows.get(0).metricType()).isEqualTo("gauge");
        assertThat(gaugeRows.get(0).value()).isEqualTo(42.0);

        // Timer -> emits multiple statistics (count, total_time, max)
        List<ServerMetricSample> timerRows = samples.stream()
                .filter(s -> s.metricName().equals("cameleer.test.timer"))
                .toList();
        assertThat(timerRows).isNotEmpty();
        // SimpleMeterRegistry emits Statistic.TOTAL ("total"); other registries (Prometheus)
        // emit TOTAL_TIME ("total_time"). Accept either so the test isn't registry-coupled.
        assertThat(timerRows).extracting(ServerMetricSample::statistic)
                .contains("count", "max");
        assertThat(timerRows).extracting(ServerMetricSample::statistic)
                .containsAnyOf("total_time", "total");
        assertThat(timerRows).allSatisfy(s ->
                assertThat(s.metricType()).isEqualTo("timer"));
        ServerMetricSample count = timerRows.stream()
                .filter(s -> s.statistic().equals("count"))
                .findFirst().orElseThrow();
        assertThat(count.value()).isEqualTo(2.0);
    }

    @Test
    void snapshot_withEmptyRegistry_doesNotWriteBatch() {
        MeterRegistry registry = new SimpleMeterRegistry();
        // Force removal of any auto-registered meters (SimpleMeterRegistry has none by default).
        RecordingStore store = new RecordingStore();
        ServerMetricsSnapshotScheduler scheduler =
                new ServerMetricsSnapshotScheduler(registry, store, "t", "s");

        scheduler.snapshot();

        assertThat(store.batches).isEmpty();
    }

    @Test
    void snapshot_swallowsStoreFailures() {
        MeterRegistry registry = new SimpleMeterRegistry();
        Counter.builder("cameleer.test").register(registry).increment();

        ServerMetricsStore throwingStore = batch -> {
            throw new RuntimeException("clickhouse down");
        };

        ServerMetricsSnapshotScheduler scheduler =
                new ServerMetricsSnapshotScheduler(registry, throwingStore, "t", "s");

        // Must not propagate — the scheduler thread would otherwise die.
        scheduler.snapshot();
    }

    private static final class RecordingStore implements ServerMetricsStore {
        final List<List<ServerMetricSample>> batches = new ArrayList<>();

        @Override
        public void insertBatch(List<ServerMetricSample> samples) {
            batches.add(List.copyOf(samples));
        }
    }
}
