#!/usr/bin/env python3
"""Compare patched Frontier MoE decomposition with vLLM's serving path."""

from __future__ import annotations

import argparse
import json
from pathlib import Path

import torch

from frontier.profiling.moe.moe_vllm_kernel import (
    profile_fused_moe_kernel,
    quantize_weights_to_fp8,
)
from vllm.model_executor.layers.fused_moe.fused_moe import (
    fused_experts,
    get_config_dtype_str,
    moe_align_block_size,
    try_get_optimal_moe_config,
)


def _measure(step, warmup: int, active: int) -> dict[str, float]:
    for _ in range(warmup):
        step()
    torch.cuda.synchronize()

    samples = []
    for _ in range(active):
        start = torch.cuda.Event(enable_timing=True)
        end = torch.cuda.Event(enable_timing=True)
        start.record()
        step()
        end.record()
        torch.cuda.synchronize()
        samples.append(start.elapsed_time(end))
    values = torch.tensor(samples)
    return {
        "min": float(values.min()),
        "median": float(values.median()),
        "mean": float(values.mean()),
        "max": float(values.max()),
        "std": float(values.std()),
    }


def _routing(num_tokens: int, top_k: int, num_experts: int, seed: int):
    generator = torch.Generator(device="cuda")
    generator.manual_seed(seed)
    topk_ids = torch.randint(
        num_experts,
        (num_tokens, top_k),
        generator=generator,
        device="cuda",
        dtype=torch.int64,
    )
    topk_weights = torch.rand(
        (num_tokens, top_k),
        generator=generator,
        device="cuda",
        dtype=torch.float32,
    )
    topk_weights /= topk_weights.sum(dim=-1, keepdim=True)
    return topk_weights, topk_ids


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--tokens", nargs="+", type=int, default=[16, 256, 1024])
    parser.add_argument("--tp", type=int, default=4)
    parser.add_argument("--ep", type=int, default=1)
    parser.add_argument("--warmup", type=int, default=2)
    parser.add_argument("--active", type=int, default=20)
    parser.add_argument("--seed", type=int, default=20260715)
    parser.add_argument("--output", type=Path, required=True)
    args = parser.parse_args()

    hidden_dim = 4096
    expert_hidden_dim = 1536
    global_num_experts = 128
    top_k = 8
    block_shape = [128, 128]
    if global_num_experts % args.ep:
        raise ValueError("EP must divide 128 experts")
    if expert_hidden_dim % args.tp:
        raise ValueError("TP must divide the expert intermediate dimension")

    local_num_experts = global_num_experts // args.ep
    local_intermediate = expert_hidden_dim // args.tp
    device = torch.device("cuda")

    torch.manual_seed(args.seed)
    w1_bf16 = torch.randn(
        local_num_experts,
        2 * local_intermediate,
        hidden_dim,
        dtype=torch.bfloat16,
        device=device,
    )
    w2_bf16 = torch.randn(
        local_num_experts,
        hidden_dim,
        local_intermediate,
        dtype=torch.bfloat16,
        device=device,
    )
    w1, w1_scale = quantize_weights_to_fp8(w1_bf16, block_shape=block_shape)
    w2, w2_scale = quantize_weights_to_fp8(w2_bf16, block_shape=block_shape)
    del w1_bf16, w2_bf16
    torch.cuda.empty_cache()

    rows = []
    for index, num_tokens in enumerate(args.tokens):
        topk_weights, topk_ids = _routing(
            num_tokens,
            top_k,
            global_num_experts,
            args.seed + index,
        )
        hidden_states = torch.randn(
            num_tokens,
            hidden_dim,
            dtype=torch.bfloat16,
            device=device,
        )

        frontier_grouped = profile_fused_moe_kernel(
            num_tokens=num_tokens,
            num_experts=local_num_experts,
            hidden_dim=hidden_dim,
            expert_hidden_dim=expert_hidden_dim,
            top_k=top_k,
            topk_weights=topk_weights,
            topk_ids=topk_ids,
            tensor_parallel_size=args.tp,
            dtype=torch.bfloat16,
            warmup_steps=args.warmup,
            active_steps=args.active,
            use_fp8=True,
            per_channel_quant=False,
            block_shape=block_shape,
            global_num_experts=global_num_experts,
        )

        config = try_get_optimal_moe_config(
            w1_shape=w1.shape,
            w2_shape=w2.shape,
            top_k=top_k,
            dtype=get_config_dtype_str(
                torch.bfloat16,
                use_fp8_w8a8=True,
            ),
            M=num_tokens,
            block_shape=block_shape,
        )

        def align_step() -> None:
            moe_align_block_size(
                topk_ids,
                config["BLOCK_SIZE_M"],
                global_num_experts,
            )

        alignment = _measure(align_step, args.warmup, args.active)

        def serving_step() -> None:
            fused_experts(
                hidden_states=hidden_states,
                w1=w1,
                w2=w2,
                topk_weights=topk_weights,
                topk_ids=topk_ids,
                inplace=True,
                use_fp8_w8a8=True,
                per_channel_quant=False,
                global_num_experts=global_num_experts,
                w1_scale=w1_scale,
                w2_scale=w2_scale,
                block_shape=block_shape,
            )

        serving = _measure(serving_step, args.warmup, args.active)
        decomposed_ms = frontier_grouped["median"] + alignment["median"]
        rows.append(
            {
                "num_tokens": num_tokens,
                "block_size_m": config["BLOCK_SIZE_M"],
                "frontier_grouped_ms": frontier_grouped,
                "frontier_alignment_ms": alignment,
                "frontier_decomposed_median_ms": decomposed_ms,
                "vllm_fused_experts_ms": serving,
                "decomposed_over_serving": decomposed_ms / serving["median"],
            }
        )

    result = {
        "contract": "Frontier grouped_gemm + shuffling alignment vs vLLM fused_experts",
        "model_shape": {
            "hidden_dim": hidden_dim,
            "expert_hidden_dim": expert_hidden_dim,
            "global_num_experts": global_num_experts,
            "local_num_experts": local_num_experts,
            "top_k": top_k,
            "tp": args.tp,
            "ep": args.ep,
            "dtype": "block_fp8_w8a8_bf16_output",
            "block_shape": block_shape,
        },
        "warmup": args.warmup,
        "active": args.active,
        "rows": rows,
    }
    args.output.parent.mkdir(parents=True, exist_ok=True)
    args.output.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
    print(json.dumps(result, indent=2))


if __name__ == "__main__":
    main()
