package com.cameleer.server.app.controller;

import com.cameleer.server.app.dto.ProcessorMetrics;
import com.cameleer.server.app.dto.RouteMetrics;
import com.cameleer.server.app.web.EnvPath;
import com.cameleer.server.core.admin.AppSettings;
import com.cameleer.server.core.admin.AppSettingsRepository;
import com.cameleer.server.core.runtime.Environment;
import com.cameleer.server.core.storage.StatsStore;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/v1/environments/{envSlug}/routes")
@Tag(name = "Route Metrics", description = "Route performance metrics (env-scoped)")
public class RouteMetricsController {

    private final JdbcTemplate jdbc;
    private final StatsStore statsStore;
    private final AppSettingsRepository appSettingsRepository;

    public RouteMetricsController(@org.springframework.beans.factory.annotation.Qualifier("clickHouseJdbcTemplate") JdbcTemplate jdbc,
                                   StatsStore statsStore,
                                   AppSettingsRepository appSettingsRepository) {
        this.jdbc = jdbc;
        this.statsStore = statsStore;
        this.appSettingsRepository = appSettingsRepository;
    }

    @GetMapping("/metrics")
    @Operation(summary = "Get route metrics for this environment",
            description = "Returns aggregated performance metrics per route for the given time window. "
                    + "Optional appId filter narrows to a single application.")
    @ApiResponse(responseCode = "200", description = "Metrics returned")
    public ResponseEntity<List<RouteMetrics>> getMetrics(
            @EnvPath Environment env,
            @RequestParam(required = false) String from,
            @RequestParam(required = false) String to,
            @RequestParam(required = false) String appId) {

        Instant toInstant = to != null ? Instant.parse(to) : Instant.now();
        Instant fromInstant = from != null ? Instant.parse(from) : toInstant.minus(24, ChronoUnit.HOURS);
        long windowSeconds = Duration.between(fromInstant, toInstant).toSeconds();

        var sql = new StringBuilder(
                "SELECT application_id, route_id, " +
                "uniqMerge(total_count) AS total, " +
                "uniqIfMerge(failed_count) AS failed, " +
                "CASE WHEN uniqMerge(total_count) > 0 THEN toFloat64(sumMerge(duration_sum)) / uniqMerge(total_count) ELSE 0 END AS avg_dur, " +
                "COALESCE(quantileMerge(0.99)(p99_duration), 0) AS p99_dur " +
                "FROM stats_1m_route WHERE bucket >= " + lit(fromInstant) + " AND bucket < " + lit(toInstant) +
                " AND environment = " + lit(env.slug()));

        if (appId != null) {
            sql.append(" AND application_id = " + lit(appId));
        }
        sql.append(" GROUP BY application_id, route_id ORDER BY application_id, route_id");

        List<RouteMetrics> metrics = jdbc.query(sql.toString(), (rs, rowNum) -> {
            String applicationId = rs.getString("application_id");
            String routeId = rs.getString("route_id");
            long total = rs.getLong("total");
            long failed = rs.getLong("failed");
            double avgDur = rs.getDouble("avg_dur");
            double p99Dur = rs.getDouble("p99_dur");

            double successRate = total > 0 ? (double) (total - failed) / total : 1.0;
            double errorRate = total > 0 ? (double) failed / total : 0.0;
            double tps = windowSeconds > 0 ? (double) total / windowSeconds : 0.0;

            return new RouteMetrics(routeId, applicationId, total, successRate,
                    avgDur, p99Dur, errorRate, tps, List.of(), -1.0);
        });

        // Sparklines
        if (!metrics.isEmpty()) {
            int sparkBuckets = 12;
            long bucketSeconds = Math.max(windowSeconds / sparkBuckets, 60);

            for (int i = 0; i < metrics.size(); i++) {
                RouteMetrics m = metrics.get(i);
                try {
                    String sparkSql = "SELECT toStartOfInterval(bucket, toIntervalSecond(" + bucketSeconds + ")) AS period, " +
                            "COALESCE(uniqMerge(total_count), 0) AS cnt " +
                            "FROM stats_1m_route WHERE bucket >= " + lit(fromInstant) + " AND bucket < " + lit(toInstant) +
                            " AND environment = " + lit(env.slug()) +
                            " AND application_id = " + lit(m.appId()) + " AND route_id = " + lit(m.routeId()) +
                            " GROUP BY period ORDER BY period";
                    List<Double> sparkline = jdbc.query(sparkSql,
                            (rs, rowNum) -> rs.getDouble("cnt"));
                    metrics.set(i, new RouteMetrics(m.routeId(), m.appId(), m.exchangeCount(),
                            m.successRate(), m.avgDurationMs(), m.p99DurationMs(),
                            m.errorRate(), m.throughputPerSec(), sparkline, m.slaCompliance()));
                } catch (Exception e) {
                    // Leave sparkline empty on error
                }
            }
        }

        // SLA compliance
        if (!metrics.isEmpty()) {
            String effectiveAppId = appId != null ? appId : metrics.get(0).appId();
            int threshold = effectiveAppId != null
                    ? appSettingsRepository.findByApplicationAndEnvironment(effectiveAppId, env.slug())
                            .map(AppSettings::slaThresholdMs).orElse(300)
                    : 300;

            Map<String, long[]> slaCounts = statsStore.slaCountsByRoute(fromInstant, toInstant,
                    effectiveAppId, threshold, env.slug());

            for (int i = 0; i < metrics.size(); i++) {
                RouteMetrics m = metrics.get(i);
                long[] counts = slaCounts.get(m.routeId());
                double sla = (counts != null && counts[1] > 0)
                        ? counts[0] * 100.0 / counts[1] : 100.0;
                metrics.set(i, new RouteMetrics(m.routeId(), m.appId(), m.exchangeCount(),
                        m.successRate(), m.avgDurationMs(), m.p99DurationMs(),
                        m.errorRate(), m.throughputPerSec(), m.sparkline(), sla));
            }
        }

        return ResponseEntity.ok(metrics);
    }

    @GetMapping("/metrics/processors")
    @Operation(summary = "Get processor metrics for this environment",
            description = "Returns aggregated performance metrics per processor for the given route and time window")
    @ApiResponse(responseCode = "200", description = "Metrics returned")
    public ResponseEntity<List<ProcessorMetrics>> getProcessorMetrics(
            @EnvPath Environment env,
            @RequestParam String routeId,
            @RequestParam(required = false) String appId,
            @RequestParam(required = false) Instant from,
            @RequestParam(required = false) Instant to) {

        Instant toInstant = to != null ? to : Instant.now();
        Instant fromInstant = from != null ? from : toInstant.minus(24, ChronoUnit.HOURS);

        var sql = new StringBuilder(
                "SELECT processor_id, processor_type, route_id, application_id, " +
                "uniqMerge(total_count) AS tc, " +
                "uniqIfMerge(failed_count) AS fc, " +
                "CASE WHEN uniqMerge(total_count) > 0 THEN toFloat64(sumMerge(duration_sum)) / uniqMerge(total_count) ELSE 0 END AS avg_duration_ms, " +
                "quantileMerge(0.99)(p99_duration) AS p99_duration_ms " +
                "FROM stats_1m_processor_detail " +
                "WHERE bucket >= " + lit(fromInstant) + " AND bucket < " + lit(toInstant) +
                " AND environment = " + lit(env.slug()) +
                " AND route_id = " + lit(routeId));

        if (appId != null) {
            sql.append(" AND application_id = " + lit(appId));
        }
        sql.append(" GROUP BY processor_id, processor_type, route_id, application_id");
        sql.append(" ORDER BY tc DESC");

        List<ProcessorMetrics> metrics = jdbc.query(sql.toString(), (rs, rowNum) -> {
            long totalCount = rs.getLong("tc");
            long failedCount = rs.getLong("fc");
            double errorRate = failedCount > 0 ? (double) failedCount / totalCount : 0.0;
            return new ProcessorMetrics(
                    rs.getString("processor_id"),
                    rs.getString("processor_type"),
                    rs.getString("route_id"),
                    rs.getString("application_id"),
                    totalCount,
                    failedCount,
                    rs.getDouble("avg_duration_ms"),
                    rs.getDouble("p99_duration_ms"),
                    errorRate);
        });

        return ResponseEntity.ok(metrics);
    }

    private static String lit(Instant instant) {
        return "'" + java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
                .withZone(java.time.ZoneOffset.UTC)
                .format(instant.truncatedTo(ChronoUnit.SECONDS)) + "'";
    }

    private static String lit(String value) {
        return "'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'";
    }
}
