package com.example.support;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * The outcome of a load run (L3git): totals, throughput, latency percentiles, optional
 * per-step (flow) breakdown, and an optional SLA verdict. When no SLA threshold was set the run is
 * report-only; when one was set {@link #slaPassed} tells the step whether to fail.
 * Generated / shipped by L3git.
 */
public class LoadResult {

    public final int total;
    public final int errors;
    public final long elapsedMs;
    public final double throughput;   // requests per second
    public final double avg;
    public final long min;
    public final long p50;
    public final long p90;
    public final long p95;
    public final long p99;
    public final long max;

    public final boolean slaChecked;
    public final boolean slaPassed;
    public final String slaMessage;

    /** Per-step latency stats when the run used a multi-step flow (empty / single entry otherwise). */
    public final List<StepStat> steps;

    public static final class StepStat {
        public final String label;
        public final int count;
        public final int errors;
        public final long p50;
        public final long p95;
        public final double avg;

        public StepStat(String label, int count, int errors, long p50, long p95, double avg) {
            this.label = label;
            this.count = count;
            this.errors = errors;
            this.p50 = p50;
            this.p95 = p95;
            this.avg = avg;
        }
    }

    public LoadResult(int total, int errors, long elapsedMs, List<Long> latencies,
                      List<StepStat> steps, boolean slaChecked, boolean slaPassed, String slaMessage) {
        this.total = total;
        this.errors = errors;
        this.elapsedMs = elapsedMs;
        this.throughput = elapsedMs > 0 ? (total * 1000.0 / elapsedMs) : 0.0;
        this.steps = steps == null ? new ArrayList<>() : steps;
        this.slaChecked = slaChecked;
        this.slaPassed = slaPassed;
        this.slaMessage = slaMessage == null ? "" : slaMessage;
        List<Long> ls = new ArrayList<>(latencies);
        Collections.sort(ls);
        if (ls.isEmpty()) {
            this.min = 0; this.p50 = 0; this.p90 = 0; this.p95 = 0; this.p99 = 0; this.max = 0;
            this.avg = 0.0;
        } else {
            this.min = ls.get(0);
            this.max = ls.get(ls.size() - 1);
            this.p50 = pct(ls, 50);
            this.p90 = pct(ls, 90);
            this.p95 = pct(ls, 95);
            this.p99 = pct(ls, 99);
            long sum = 0;
            for (long l : ls) {
                sum += l;
            }
            this.avg = (double) sum / ls.size();
        }
    }

    /** Percentile of an already-sorted list (nearest-rank). Package-visible so LoadRunner reuses it. */
    static long pct(List<Long> sorted, int p) {
        if (sorted.isEmpty()) {
            return 0;
        }
        int idx = (int) Math.ceil(p / 100.0 * sorted.size()) - 1;
        if (idx < 0) {
            idx = 0;
        }
        if (idx >= sorted.size()) {
            idx = sorted.size() - 1;
        }
        return sorted.get(idx);
    }

    public String report() {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("Load result: %d requests in %d ms (%.1f req/s), errors=%d", total, elapsedMs, throughput, errors));
        sb.append(System.lineSeparator());
        sb.append(String.format("  latency ms  avg=%.1f  min=%d  p50=%d  p90=%d  p95=%d  p99=%d  max=%d",
                avg, min, p50, p90, p95, p99, max));
        if (steps.size() > 1) {
            for (StepStat s : steps) {
                sb.append(System.lineSeparator());
                sb.append(String.format("  step '%s'  %d req, errors=%d, avg=%.1f p50=%d p95=%d",
                        s.label, s.count, s.errors, s.avg, s.p50, s.p95));
            }
        }
        if (slaChecked) {
            sb.append(System.lineSeparator());
            sb.append("  SLA: ").append(slaPassed ? "PASS" : "FAIL").append(" - ").append(slaMessage);
        }
        return sb.toString();
    }
}
