import os
import sys
from pathlib import Path

import numpy as np
import pytest
import torch
from PIL import Image
from vllm.distributed.parallel_state import cleanup_dist_env_and_memory

from vllm_omni.diffusion.data import DiffusionParallelConfig
from vllm_omni.inputs.data import OmniDiffusionSamplingParams

# ruff: noqa: E402
REPO_ROOT = Path(__file__).resolve().parents[2]
if str(REPO_ROOT) not in sys.path:
    sys.path.insert(0, str(REPO_ROOT))

import time

from tests.helpers.runtime import OmniRunner
from vllm_omni.platforms import current_omni_platform

os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"

all_model_configs = [
    {
        "model_name": "Wan-AI/Wan2.2-TI2V-5B-Diffusers",
        "out_width": 1280,
        "out_height": 720,
        "out_frames": 16,
    },
    {
        "model_name": "Wan-AI/Wan2.2-T2V-A14B-Diffusers",
        "out_width": 1280,
        "out_height": 720,
        "out_frames": 16,
    },
    {
        "model_name": "Wan-AI/Wan2.2-I2V-A14B-Diffusers",
        "out_width": 1280,
        "out_height": 720,
        "out_frames": 16,
    },
    {
        "model_name": "Qwen/Qwen-Image",
        "out_width": 3072,
        "out_height": 3072,
        "out_frames": 1,
    },
    # {
    #     "model_name": "stepfun-ai/NextStep-1.1",
    #     "out_width": 3072,
    #     "out_height": 3072,
    #     "out_frames": 1,
    # },
]


def is_nextstep_model(model_name: str) -> bool:
    """Check if the model is a NextStep model by reading its config."""
    from vllm.transformers_utils.config import get_hf_file_to_dict

    try:
        cfg = get_hf_file_to_dict("config.json", model_name)
        if cfg and cfg.get("model_type") == "nextstep":
            return True
    except Exception:
        pass
    return False


def model_run(model_configs, tp, out_height, out_width, out_frames, using_tile, vae_patch_parallel_size=1):
    parallel_config = DiffusionParallelConfig(
        tensor_parallel_size=tp,
        vae_patch_parallel_size=vae_patch_parallel_size,
    )

    omni_kwargs = {
        "vae_use_tiling": using_tile,
        "parallel_config": parallel_config,
    }
    use_nextstep = is_nextstep_model(model_configs["model_name"])
    if use_nextstep:
        # NextStep-1.1 requires explicit pipeline class
        omni_kwargs["model_class_name"] = "NextStep11Pipeline"

    with OmniRunner(model_configs["model_name"], **omni_kwargs) as runner:
        m = runner.omni
        image = Image.new("RGB", (out_width, out_height), (0, 0, 0))
        start = time.perf_counter()
        outputs = m.generate(
            {
                "prompt": "A cat sitting on a table",
                "multi_modal_data": {"image": image},
            },
            sampling_params_list=OmniDiffusionSamplingParams(
                height=model_configs["out_height"],
                width=model_configs["out_width"],
                num_frames=model_configs["out_frames"],
                num_inference_steps=2,
                generator=torch.Generator(current_omni_platform.device_type).manual_seed(42),
            ),
        )
        end = time.perf_counter()
        first_output = outputs[0]
        req_out = first_output.request_output
        frames = req_out.images[0]
        if isinstance(frames, torch.Tensor):
            frames = frames.detach().cpu().numpy()
        elif isinstance(frames, Image.Image):
            frames = np.array(frames)
        # frames shape: (batch, num_frames, height, width, channels)
        cost = (end - start) * 1000
        return frames, cost
        cleanup_dist_env_and_memory()


@pytest.mark.parametrize("model_configs", all_model_configs)
def test_vae_parallel_model(model_configs: str):
    parallel_result, parallel_time = model_run(
        model_configs=model_configs,
        tp=2,
        out_width=1280,
        out_height=704,
        out_frames=5,
        using_tile=True,
        vae_patch_parallel_size=2,
    )
    non_parallel_result, non_parallel_time = model_run(
        model_configs=model_configs,
        tp=2,
        out_width=1280,
        out_height=704,
        out_frames=5,
        using_tile=True,
        vae_patch_parallel_size=1,
    )
    result_diff = np.abs(non_parallel_result - parallel_result)

    mean_threshold = 3e-2
    max_threshold = 3e-2  # they should be totally same
    print(
        f"{model_configs['model_name']} TP = 2 (tile + parallel vs tile): "
        f"mean_abs_diff={result_diff.mean():.6e}, max_abs_diff={result_diff.max():.6e}; "
        f"thresholds: mean<={mean_threshold:.6e}, max<={max_threshold:.6e}; "
        f"parallel generate take time: {parallel_time:.2f} ms, non-parallel take time: {non_parallel_time:.2f} ms"
    )
    # assert non_parallel_time > parallel_time
    assert result_diff.mean() < mean_threshold
    assert result_diff.max() < max_threshold
