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

import com.cameleer.server.core.alerting.AggregationOp;
import com.cameleer.server.core.alerting.AlertRule;
import com.cameleer.server.core.alerting.ConditionKind;
import com.cameleer.server.core.alerting.JvmMetricCondition;
import com.cameleer.server.core.storage.MetricsQueryStore;
import com.cameleer.server.core.storage.model.MetricTimeSeries;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.OptionalDouble;

@Component
public class JvmMetricEvaluator implements ConditionEvaluator<JvmMetricCondition> {

    private final MetricsQueryStore metricsStore;

    public JvmMetricEvaluator(MetricsQueryStore metricsStore) {
        this.metricsStore = metricsStore;
    }

    @Override
    public ConditionKind kind() { return ConditionKind.JVM_METRIC; }

    @Override
    public EvalResult evaluate(JvmMetricCondition c, AlertRule rule, EvalContext ctx) {
        String agentId = c.scope() != null ? c.scope().agentId() : null;
        if (agentId == null) return EvalResult.Clear.INSTANCE;

        Map<String, List<MetricTimeSeries.Bucket>> series = metricsStore.queryTimeSeries(
                agentId,
                List.of(c.metric()),
                ctx.now().minusSeconds(c.windowSeconds()),
                ctx.now(),
                1
        );

        List<MetricTimeSeries.Bucket> buckets = series.get(c.metric());
        if (buckets == null || buckets.isEmpty()) return EvalResult.Clear.INSTANCE;

        OptionalDouble aggregated = aggregate(buckets, c.aggregation());
        if (aggregated.isEmpty()) return EvalResult.Clear.INSTANCE;

        double actual = aggregated.getAsDouble();

        boolean fire = switch (c.comparator()) {
            case GT  -> actual >  c.threshold();
            case GTE -> actual >= c.threshold();
            case LT  -> actual <  c.threshold();
            case LTE -> actual <= c.threshold();
            case EQ  -> actual == c.threshold();
        };

        if (!fire) return EvalResult.Clear.INSTANCE;

        return new EvalResult.Firing(actual, c.threshold(),
                Map.of(
                        "metric", c.metric(),
                        "agent",  Map.of("id", agentId)
                )
        );
    }

    private OptionalDouble aggregate(List<MetricTimeSeries.Bucket> buckets, AggregationOp op) {
        return switch (op) {
            case MAX    -> buckets.stream().mapToDouble(MetricTimeSeries.Bucket::value).max();
            case MIN    -> buckets.stream().mapToDouble(MetricTimeSeries.Bucket::value).min();
            case AVG    -> buckets.stream().mapToDouble(MetricTimeSeries.Bucket::value).average();
            case LATEST -> buckets.stream()
                    .max(java.util.Comparator.comparing(MetricTimeSeries.Bucket::time))
                    .map(b -> OptionalDouble.of(b.value()))
                    .orElse(OptionalDouble.empty());
        };
    }
}
