"""Common utilities for jit_kernel benchmark files."""

from typing import Callable, List, Optional, Sequence, Tuple

import torch
import triton.testing

from sglang.kernels.ops.communication.mp import multigpu_launch
from sglang.utils import is_in_ci


def multigpu_bench_main(
    name: str,
    file: str,
    num_gpus: Sequence[int],
    main_fn: Callable[[], None],
    *,
    pre_launch_fn: Optional[Callable[[List[int]], None]] = None,
    timeout: Optional[int] = None,
) -> None:
    """Torchrun-based multi-GPU benchmark entry point.

    Drop this at the bottom of a benchmark file::

        multigpu_bench_main(
            name=__name__,
            file=__file__,
            num_gpus=range(2, 9),
            main_fn=benchmark.run,
        )

    Mirrors :func:`multigpu_pytest_main` but invokes a caller-supplied function
    instead of pytest. ``main_fn`` is expected to return ``None`` on success;
    any exception propagates as a non-zero exit. Pass ``--num-gpu 2,4`` on the
    command line to override ``num_gpus``.

    ``pre_launch_fn`` (kw-only) runs once in the outer process before any
    torchrun child starts, receiving the runnable world sizes. Use it for
    parallel JIT precompilation so torchrun children hit a warm disk cache.

    ``timeout`` (kw-only, seconds) bounds each per-world-size torchrun
    invocation. Defaults to ``None`` (wait indefinitely) since benchmark sweeps
    can legitimately run long; set it to fail fast on a hung worker.
    """

    def inner() -> int:
        main_fn()
        return 0

    return multigpu_launch(
        name,
        file,
        num_gpus,
        env_key="_IS_BENCH_MULTIGPU_SGLANG_JIT_KERNEL",
        inner=inner,
        kind="benchmark",
        pre_launch_fn=pre_launch_fn,
        timeout=timeout,
    )


# Common constants
DEFAULT_DTYPE = torch.bfloat16
DEFAULT_DEVICE = "cuda"
DEFAULT_QUANTILES = [0.5, 0.2, 0.8]


def create_empty(*shape: int, dtype=DEFAULT_DTYPE, device=DEFAULT_DEVICE):
    return torch.empty(shape, dtype=dtype, device=device)


def create_random(*shape: int, dtype=DEFAULT_DTYPE, device=DEFAULT_DEVICE):
    return torch.randn(shape, dtype=dtype, device=device)


def get_benchmark_range(full_range: List, ci_range: List) -> List:
    """Return appropriate benchmark range based on CI environment."""
    return ci_range if is_in_ci() else full_range


def run_benchmark(
    fn: Callable,
    quantiles: Sequence[float] = (),
    scale: float = 1.0,
) -> Tuple[float, float, float]:
    """Execute benchmark using CUDA graph and return times in microseconds.

    Args:
        fn: Function to benchmark
        quantiles: Quantiles for timing measurements [median, min, max]
        scale: Scale the result down (usually num_layers).

    Returns:
        Tuple of (median_us, max_us, min_us)
    """
    quantiles = list(quantiles or DEFAULT_QUANTILES)
    ms, min_ms, max_ms = triton.testing.do_bench_cudagraph(fn, quantiles=quantiles)
    return 1000 * ms / scale, 1000 * max_ms / scale, 1000 * min_ms / scale


def run_benchmark_no_cudagraph(
    fn: Callable,
    quantiles: Sequence[float] = (),
    scale: float = 1.0,
) -> Tuple[float, float, float]:
    quantiles = list(quantiles or DEFAULT_QUANTILES)
    ms, min_ms, max_ms = triton.testing.do_bench(fn, quantiles=quantiles)
    return 1000 * ms / scale, 1000 * max_ms / scale, 1000 * min_ms / scale
