package com.cameleer.server.app.alerting.eval;

import com.cameleer.server.core.alerting.*;
import com.cameleer.server.core.storage.MetricsQueryStore;
import com.cameleer.server.core.storage.model.MetricTimeSeries;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class JvmMetricEvaluatorTest {

    private MetricsQueryStore metricsStore;
    private JvmMetricEvaluator eval;

    private static final UUID ENV_ID  = UUID.fromString("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb");
    private static final UUID RULE_ID = UUID.fromString("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
    private static final Instant NOW  = Instant.parse("2026-04-19T10:00:00Z");

    @BeforeEach
    void setUp() {
        metricsStore = mock(MetricsQueryStore.class);
        eval = new JvmMetricEvaluator(metricsStore);
    }

    private AlertRule ruleWith(AlertCondition condition) {
        return new AlertRule(RULE_ID, ENV_ID, "test", null,
                AlertSeverity.CRITICAL, true, condition.kind(), condition,
                60, 0, 0, null, null, List.of(), List.of(),
                null, null, null, Map.of(), null, null, null, null);
    }

    private MetricTimeSeries.Bucket bucket(double value) {
        return new MetricTimeSeries.Bucket(NOW.minusSeconds(10), value);
    }

    @Test
    void firesWhenMaxExceedsThreshold() {
        var condition = new JvmMetricCondition(
                new AlertScope(null, null, "agent-1"),
                "heap_used_percent", AggregationOp.MAX, Comparator.GT, 90.0, 300);

        when(metricsStore.queryTimeSeries(eq("agent-1"), eq(List.of("heap_used_percent")), any(), any(), eq(1)))
                .thenReturn(Map.of("heap_used_percent", List.of(bucket(95.0))));

        EvalResult r = eval.evaluate(condition, ruleWith(condition), new EvalContext("default", NOW, new TickCache()));
        assertThat(r).isInstanceOf(EvalResult.Firing.class);
        var f = (EvalResult.Firing) r;
        assertThat(f.currentValue()).isEqualTo(95.0);
        assertThat(f.threshold()).isEqualTo(90.0);
    }

    @Test
    void clearWhenMaxBelowThreshold() {
        var condition = new JvmMetricCondition(
                new AlertScope(null, null, "agent-1"),
                "heap_used_percent", AggregationOp.MAX, Comparator.GT, 90.0, 300);

        when(metricsStore.queryTimeSeries(eq("agent-1"), eq(List.of("heap_used_percent")), any(), any(), eq(1)))
                .thenReturn(Map.of("heap_used_percent", List.of(bucket(80.0))));

        EvalResult r = eval.evaluate(condition, ruleWith(condition), new EvalContext("default", NOW, new TickCache()));
        assertThat(r).isEqualTo(EvalResult.Clear.INSTANCE);
    }

    @Test
    void aggregatesMultipleBucketsWithMax() {
        var condition = new JvmMetricCondition(
                new AlertScope(null, null, "agent-1"),
                "heap_used_percent", AggregationOp.MAX, Comparator.GT, 90.0, 300);

        when(metricsStore.queryTimeSeries(eq("agent-1"), eq(List.of("heap_used_percent")), any(), any(), eq(1)))
                .thenReturn(Map.of("heap_used_percent",
                        List.of(bucket(70.0), bucket(95.0), bucket(85.0))));

        EvalResult r = eval.evaluate(condition, ruleWith(condition), new EvalContext("default", NOW, new TickCache()));
        assertThat(r).isInstanceOf(EvalResult.Firing.class);
        assertThat(((EvalResult.Firing) r).currentValue()).isEqualTo(95.0);
    }

    @Test
    void aggregatesWithMin() {
        var condition = new JvmMetricCondition(
                new AlertScope(null, null, "agent-1"),
                "heap_free_percent", AggregationOp.MIN, Comparator.LT, 10.0, 300);

        when(metricsStore.queryTimeSeries(eq("agent-1"), eq(List.of("heap_free_percent")), any(), any(), eq(1)))
                .thenReturn(Map.of("heap_free_percent",
                        List.of(bucket(20.0), bucket(8.0), bucket(15.0))));

        EvalResult r = eval.evaluate(condition, ruleWith(condition), new EvalContext("default", NOW, new TickCache()));
        assertThat(r).isInstanceOf(EvalResult.Firing.class);
        assertThat(((EvalResult.Firing) r).currentValue()).isEqualTo(8.0);
    }

    @Test
    void aggregatesWithAvg() {
        var condition = new JvmMetricCondition(
                new AlertScope(null, null, "agent-1"),
                "cpu_usage", AggregationOp.AVG, Comparator.GT, 50.0, 300);

        when(metricsStore.queryTimeSeries(eq("agent-1"), eq(List.of("cpu_usage")), any(), any(), eq(1)))
                .thenReturn(Map.of("cpu_usage",
                        List.of(bucket(40.0), bucket(60.0), bucket(80.0))));

        EvalResult r = eval.evaluate(condition, ruleWith(condition), new EvalContext("default", NOW, new TickCache()));
        // avg = 60.0 > 50 → fires
        assertThat(r).isInstanceOf(EvalResult.Firing.class);
        assertThat(((EvalResult.Firing) r).currentValue()).isEqualTo(60.0);
    }

    @Test
    void aggregatesWithLatest() {
        var condition = new JvmMetricCondition(
                new AlertScope(null, null, "agent-1"),
                "thread_count", AggregationOp.LATEST, Comparator.GT, 200.0, 300);

        Instant t1 = NOW.minusSeconds(30);
        Instant t2 = NOW.minusSeconds(10);
        when(metricsStore.queryTimeSeries(eq("agent-1"), eq(List.of("thread_count")), any(), any(), eq(1)))
                .thenReturn(Map.of("thread_count", List.of(
                        new MetricTimeSeries.Bucket(t1, 180.0),
                        new MetricTimeSeries.Bucket(t2, 250.0)
                )));

        EvalResult r = eval.evaluate(condition, ruleWith(condition), new EvalContext("default", NOW, new TickCache()));
        assertThat(r).isInstanceOf(EvalResult.Firing.class);
        assertThat(((EvalResult.Firing) r).currentValue()).isEqualTo(250.0);
    }

    @Test
    void clearWhenNoBucketsReturned() {
        var condition = new JvmMetricCondition(
                new AlertScope(null, null, "agent-1"),
                "heap_used_percent", AggregationOp.MAX, Comparator.GT, 90.0, 300);

        when(metricsStore.queryTimeSeries(eq("agent-1"), eq(List.of("heap_used_percent")), any(), any(), eq(1)))
                .thenReturn(Map.of());

        EvalResult r = eval.evaluate(condition, ruleWith(condition), new EvalContext("default", NOW, new TickCache()));
        assertThat(r).isEqualTo(EvalResult.Clear.INSTANCE);
    }

    @Test
    void kindIsJvmMetric() {
        assertThat(eval.kind()).isEqualTo(ConditionKind.JVM_METRIC);
    }
}
