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

import com.cameleer.server.core.alerting.*;
import com.cameleer.server.core.runtime.Environment;
import com.cameleer.server.core.runtime.EnvironmentRepository;
import com.cameleer.server.core.search.ExecutionStats;
import com.cameleer.server.core.storage.StatsStore;
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.Optional;
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 RouteMetricEvaluatorTest {

    private StatsStore statsStore;
    private EnvironmentRepository envRepo;
    private RouteMetricEvaluator 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() {
        statsStore = mock(StatsStore.class);
        envRepo    = mock(EnvironmentRepository.class);
        eval = new RouteMetricEvaluator(statsStore, envRepo);

        var env = new Environment(ENV_ID, "prod", "Production", false, true, null, null, "slate", null);
        when(envRepo.findById(ENV_ID)).thenReturn(Optional.of(env));
    }

    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 ExecutionStats stats(long total, long failed, long p99) {
        return new ExecutionStats(total, failed, 100L, p99, 0L, 0L, 0L, 0L, 0L, 0L);
    }

    @Test
    void firesWhenP99ExceedsThreshold() {
        var condition = new RouteMetricCondition(
                new AlertScope("orders", null, null),
                RouteMetric.P99_LATENCY_MS, Comparator.GT, 2000.0, 300);
        when(statsStore.statsForApp(any(), any(), eq("orders"), eq("prod")))
                .thenReturn(stats(100, 5, 2500));

        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(2500.0);
        assertThat(f.threshold()).isEqualTo(2000.0);
    }

    @Test
    void clearWhenP99BelowThreshold() {
        var condition = new RouteMetricCondition(
                new AlertScope("orders", null, null),
                RouteMetric.P99_LATENCY_MS, Comparator.GT, 2000.0, 300);
        when(statsStore.statsForApp(any(), any(), eq("orders"), eq("prod")))
                .thenReturn(stats(100, 5, 1500));

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

    @Test
    void firesOnErrorRate() {
        // 50/100 = 50% error rate, threshold 0.3 GT
        var condition = new RouteMetricCondition(
                new AlertScope("orders", null, null),
                RouteMetric.ERROR_RATE, Comparator.GT, 0.3, 300);
        when(statsStore.statsForApp(any(), any(), eq("orders"), eq("prod")))
                .thenReturn(stats(100, 50, 500));

        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(0.5);
    }

    @Test
    void errorRateZeroWhenNoExecutions() {
        var condition = new RouteMetricCondition(
                new AlertScope("orders", null, null),
                RouteMetric.ERROR_RATE, Comparator.GT, 0.1, 300);
        when(statsStore.statsForApp(any(), any(), eq("orders"), eq("prod")))
                .thenReturn(stats(0, 0, 0));

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

    @Test
    void routeScopedUsesStatsForRoute() {
        var condition = new RouteMetricCondition(
                new AlertScope("orders", "direct:process", null),
                RouteMetric.THROUGHPUT, Comparator.LT, 10.0, 300);
        when(statsStore.statsForRoute(any(), any(), eq("direct:process"), eq("orders"), eq("prod")))
                .thenReturn(stats(5, 0, 100));

        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(5.0);
    }

    @Test
    void envWideScopeUsesGlobalStats() {
        var condition = new RouteMetricCondition(
                new AlertScope(null, null, null),
                RouteMetric.ERROR_COUNT, Comparator.GTE, 5.0, 300);
        when(statsStore.stats(any(), any(), eq("prod")))
                .thenReturn(stats(100, 10, 200));

        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(10.0);
    }

    @Test
    void kindIsRouteMetric() {
        assertThat(eval.kind()).isEqualTo(ConditionKind.ROUTE_METRIC);
    }
}
