# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Regression test for #2686: pre-quantized methods must not apply
quant config to vision / audio encoders.

For modelopt FP8/FP4/MXFP8 checkpoints the Thinker LM is the only
quantized component.  Vision and audio encoder weights are BF16 with no
FP8 scale tensors — passing quant_config to them causes FP8 kernels to
run on BF16 weights, producing garbage embeddings.

**Import note (pytest + ``tests/model_executor`` only):** :mod:`vllm_omni.quantization`
:file:`__init__` pulls ``factory``/``inc`` and reproduces vLLM CustomOp double
registration.  We load the same :file:`component_config.py` via
:data:`importlib` under a *non-package* name so the package ``__init__`` is
never executed for this file.
"""

from __future__ import annotations

import importlib.util
import sys
from pathlib import Path
from unittest.mock import MagicMock

import pytest

_CC_MODULE_NAME = "_vllm_omni_tests_component_config_encoder_quant"


def _load_component_config():
    if _CC_MODULE_NAME in sys.modules:
        return sys.modules[_CC_MODULE_NAME]
    root = Path(__file__).resolve().parents[3]
    path = root / "vllm_omni" / "quantization" / "component_config.py"
    if not path.is_file():
        raise FileNotFoundError(f"expected component_config at {path}")
    spec = importlib.util.spec_from_file_location(_CC_MODULE_NAME, path)
    if spec is None or spec.loader is None:
        raise RuntimeError("failed to create import spec for component_config")
    mod = importlib.util.module_from_spec(spec)
    sys.modules[_CC_MODULE_NAME] = mod
    spec.loader.exec_module(mod)
    return mod


_cc = _load_component_config()
PRE_QUANTIZED_METHODS = _cc.PRE_QUANTIZED_METHODS
ComponentQuantizationConfig = _cc.ComponentQuantizationConfig
resolve_encoder_quant_config = _cc.resolve_encoder_quant_config

pytestmark = [pytest.mark.core_model, pytest.mark.cpu]

# ---------------------------------------------------------------------------
# resolve_encoder_quant_config — the core routing logic for encoder quant
# ---------------------------------------------------------------------------


@pytest.mark.parametrize("method", sorted(PRE_QUANTIZED_METHODS))
def test_pre_quantized_returns_none(method: str) -> None:
    """visual_quant_config and audio_quant_config must be None for
    pre-quantized methods (modelopt, modelopt_fp4, modelopt_mxfp8,
    modelopt_mixed)."""
    mock_config = MagicMock()
    mock_config.get_name.return_value = method

    assert resolve_encoder_quant_config(mock_config) is None


@pytest.mark.parametrize("method", ["fp8", "awq", "gptq", "bitsandbytes"])
def test_non_pre_quantized_preserves_config(method: str) -> None:
    """Non-pre-quantized methods should pass through the original config."""
    mock_config = MagicMock()
    mock_config.get_name.return_value = method

    assert resolve_encoder_quant_config(mock_config) is mock_config


def test_none_input_returns_none() -> None:
    """No quantization → None for encoders."""
    assert resolve_encoder_quant_config(None) is None


def test_component_config_passed_through() -> None:
    """ComponentQuantizationConfig should be returned as-is so the caller
    can call .resolve() with the appropriate prefix."""
    inner = MagicMock()
    inner.get_name.return_value = "modelopt"  # would be None if not Component
    component = ComponentQuantizationConfig(
        component_configs={"language_model": inner},
        default_config=None,
    )

    result = resolve_encoder_quant_config(component)
    assert result is component


# ---------------------------------------------------------------------------
# PRE_QUANTIZED_METHODS constant — exhaustiveness check
# ---------------------------------------------------------------------------


def test_pre_quantized_methods_contains_expected() -> None:
    """Guard against accidental removal of a known pre-quantized method."""
    expected = {"modelopt", "modelopt_fp4", "modelopt_mxfp8", "modelopt_mixed"}
    assert PRE_QUANTIZED_METHODS == expected
