from __future__ import annotations

from dataclasses import dataclass
from math import ceil


@dataclass(frozen=True)
class BenchmarkStats:
    count: int
    total_latency_ns: int
    total_elapsed_ns: int
    min_us: float
    p50_us: float
    p95_us: float
    p99_us: float
    max_us: float
    mean_us: float
    total_elapsed_ms: float
    throughput_per_sec: float

    @classmethod
    def from_iteration_nanos(cls, iteration_durations_ns: list[int], total_elapsed_ns: int) -> "BenchmarkStats":
        if not iteration_durations_ns:
            raise ValueError("Benchmark iterations must not be empty.")

        sorted_durations_ns = sorted(max(0, duration_ns) for duration_ns in iteration_durations_ns)
        total_latency_ns = sum(sorted_durations_ns)
        count = len(sorted_durations_ns)
        total_elapsed_ms = total_elapsed_ns / 1_000_000.0
        throughput_per_sec = 0.0 if total_elapsed_ns <= 0 else count / (total_elapsed_ns / 1_000_000_000.0)
        return cls(
            count=count,
            total_latency_ns=total_latency_ns,
            total_elapsed_ns=total_elapsed_ns,
            min_us=sorted_durations_ns[0] / 1_000.0,
            p50_us=_percentile_micros(sorted_durations_ns, 0.50),
            p95_us=_percentile_micros(sorted_durations_ns, 0.95),
            p99_us=_percentile_micros(sorted_durations_ns, 0.99),
            max_us=sorted_durations_ns[-1] / 1_000.0,
            mean_us=(total_latency_ns / 1_000.0) / count,
            total_elapsed_ms=total_elapsed_ms,
            throughput_per_sec=throughput_per_sec,
        )


@dataclass(frozen=True)
class BenchmarkSummary:
    runs: int
    overall_mean_us: float
    overall_elapsed_ms: float
    best_p50_us: float
    best_p99_us: float
    best_throughput_per_sec: float


def summarize_runs(run_stats: list[BenchmarkStats]) -> BenchmarkSummary:
    if not run_stats:
        return BenchmarkSummary(
            runs=0,
            overall_mean_us=0.0,
            overall_elapsed_ms=0.0,
            best_p50_us=0.0,
            best_p99_us=0.0,
            best_throughput_per_sec=0.0,
        )

    total_latency_ns = sum(stats.total_latency_ns for stats in run_stats)
    total_elapsed_ns = sum(stats.total_elapsed_ns for stats in run_stats)
    total_count = sum(stats.count for stats in run_stats)
    return BenchmarkSummary(
        runs=len(run_stats),
        overall_mean_us=0.0 if total_count == 0 else (total_latency_ns / 1_000.0) / total_count,
        overall_elapsed_ms=total_elapsed_ns / 1_000_000.0,
        best_p50_us=min(stats.p50_us for stats in run_stats),
        best_p99_us=min(stats.p99_us for stats in run_stats),
        best_throughput_per_sec=max(stats.throughput_per_sec for stats in run_stats),
    )


def _percentile_micros(sorted_durations_ns: list[int], percentile_value: float) -> float:
    if len(sorted_durations_ns) == 1:
        return sorted_durations_ns[0] / 1_000.0
    percentile_rank = ceil(percentile_value * len(sorted_durations_ns))
    percentile_index = max(0, min(len(sorted_durations_ns) - 1, percentile_rank - 1))
    return sorted_durations_ns[percentile_index] / 1_000.0
