package com.cameleer.server.app.storage;

import com.cameleer.server.core.storage.MetricsQueryStore;
import com.cameleer.server.core.storage.model.MetricTimeSeries;
import org.springframework.jdbc.core.JdbcTemplate;

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

public class ClickHouseMetricsQueryStore implements MetricsQueryStore {

    private final String tenantId;
    private final JdbcTemplate jdbc;

    public ClickHouseMetricsQueryStore(String tenantId, JdbcTemplate jdbc) {
        this.tenantId = tenantId;
        this.jdbc = jdbc;
    }

    @Override
    public Map<String, List<MetricTimeSeries.Bucket>> queryTimeSeries(
            String instanceId, List<String> metricNames,
            Instant from, Instant to, int buckets) {

        long intervalSeconds = Math.max(60,
                (to.getEpochSecond() - from.getEpochSecond()) / Math.max(buckets, 1));

        Map<String, List<MetricTimeSeries.Bucket>> result = new LinkedHashMap<>();
        for (String name : metricNames) {
            result.put(name.trim(), new ArrayList<>());
        }

        String[] namesArray = metricNames.stream().map(String::trim).toArray(String[]::new);

        String placeholders = String.join(", ", Collections.nCopies(namesArray.length, "?"));

        String finalSql = """
            SELECT toStartOfInterval(collected_at, INTERVAL %d SECOND) AS bucket,
                   metric_name,
                   avg(metric_value) AS avg_value
            FROM agent_metrics
            WHERE tenant_id = ?
              AND instance_id = ?
              AND collected_at >= ?
              AND collected_at < ?
              AND metric_name IN (%s)
            GROUP BY bucket, metric_name
            ORDER BY bucket
            """.formatted(intervalSeconds, placeholders);

        List<Object> params = new ArrayList<>();
        params.add(tenantId);
        params.add(instanceId);
        params.add(java.sql.Timestamp.from(from));
        params.add(java.sql.Timestamp.from(to));
        Collections.addAll(params, namesArray);

        jdbc.query(finalSql, rs -> {
            String metricName = rs.getString("metric_name");
            Instant bucket = rs.getTimestamp("bucket").toInstant();
            double value = rs.getDouble("avg_value");
            result.computeIfAbsent(metricName, k -> new ArrayList<>())
                    .add(new MetricTimeSeries.Bucket(bucket, value));
        }, params.toArray());

        return result;
    }

    @Override
    public Map<String, List<MetricTimeSeries.Bucket>> queryTimeSeriesDelta(
            String instanceId, List<String> metricNames,
            Instant from, Instant to, int buckets) {

        long intervalSeconds = Math.max(60,
                (to.getEpochSecond() - from.getEpochSecond()) / Math.max(buckets, 1));

        Map<String, List<MetricTimeSeries.Bucket>> result = new LinkedHashMap<>();
        for (String name : metricNames) {
            result.put(name.trim(), new ArrayList<>());
        }

        String[] namesArray = metricNames.stream().map(String::trim).toArray(String[]::new);
        String placeholders = String.join(", ", Collections.nCopies(namesArray.length, "?"));

        String finalSql = """
            SELECT bucket, metric_name,
                   greatest(0, max_val - lagInFrame(max_val, 1, max_val)
                       OVER (PARTITION BY metric_name ORDER BY bucket
                       ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)) AS avg_value
            FROM (
                SELECT toStartOfInterval(collected_at, INTERVAL %d SECOND) AS bucket,
                       metric_name,
                       max(metric_value) AS max_val
                FROM agent_metrics
                WHERE tenant_id = ?
                  AND instance_id = ?
                  AND collected_at >= ?
                  AND collected_at < ?
                  AND metric_name IN (%s)
                GROUP BY bucket, metric_name
            )
            ORDER BY bucket
            """.formatted(intervalSeconds, placeholders);

        List<Object> params = new ArrayList<>();
        params.add(tenantId);
        params.add(instanceId);
        params.add(java.sql.Timestamp.from(from));
        params.add(java.sql.Timestamp.from(to));
        Collections.addAll(params, namesArray);

        jdbc.query(finalSql, rs -> {
            String metricName = rs.getString("metric_name");
            Instant bucket = rs.getTimestamp("bucket").toInstant();
            double value = rs.getDouble("avg_value");
            result.computeIfAbsent(metricName, k -> new ArrayList<>())
                    .add(new MetricTimeSeries.Bucket(bucket, value));
        }, params.toArray());

        return result;
    }
}
