# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0

import importlib
import sys

import pytest

from cuda.core._utils import enum_explanations_helpers
from cuda.core._utils.enum_explanations_helpers import (
    DocstringBackedExplanations,
    _binding_version_has_usable_enum_docstrings,
    clean_enum_member_docstring,
)


class _FakeEnumMember:
    def __init__(self, doc):
        self.__doc__ = doc


class _FakeEnumType:
    def __init__(self, members):
        self._members = members

    def __call__(self, code):
        try:
            return self._members[code]
        except KeyError as e:
            raise ValueError(code) from e


@pytest.mark.parametrize(
    ("raw", "expected"),
    [
        pytest.param("a\nb  c", "a b c", id="collapse_whitespace"),
        pytest.param("  x  \n ", "x", id="strip_padding"),
        pytest.param(
            "see\n:py:obj:`~.cuInit()` or :py:obj:`cuCtxDestroy()`",
            "see cuInit() or cuCtxDestroy()",
            id="rst_py_domain_role",
        ),
        pytest.param(
            "x :py:func:`~.cudaMalloc()` y",
            "x cudaMalloc() y",
            id="rst_py_role",
        ),
        pytest.param(
            "x :c:func:`cuLaunchKernel` y",
            "x cuLaunchKernel y",
            id="rst_non_py_domain_role",
        ),
        pytest.param("x :term:`device` y", "x device y", id="rst_role_without_domain"),
        pytest.param("**Note:** text", "Note: text", id="strip_bold"),
        pytest.param("*Note* text", "Note text", id="strip_italic"),
        pytest.param("[Deprecated]\n", "[Deprecated]", id="deprecated_line"),
        pytest.param("non- linear", "non-linear", id="hyphen_space_after"),
        pytest.param("word -word", "word-word", id="hyphen_space_before"),
        pytest.param("GPU- Direct", "GPU-Direct", id="hyphen_space_after_uppercase"),
        pytest.param("peer -GPU", "peer-GPU", id="hyphen_space_before_uppercase"),
        pytest.param("L2- cache", "L2-cache", id="hyphen_space_after_digit"),
        pytest.param(
            "Common causes are - a. bad access",
            "Common causes are - a. bad access",
            id="preserve_dash_separator",
        ),
        pytest.param(
            'Please see\n:py:obj:`~.Interactions`with the CUDA Driver API" for more information.',
            'Please see "Interactions with the CUDA Driver API" for more information.',
            id="codegen_broken_interactions_role",
        ),
    ],
)
def test_clean_enum_member_docstring_examples(raw, expected):
    assert clean_enum_member_docstring(raw) == expected


def test_clean_enum_member_docstring_none_input():
    assert clean_enum_member_docstring(None) is None


def test_docstring_backed_get_returns_default_for_non_enum_code():
    lut = DocstringBackedExplanations(_FakeEnumType({}))
    assert lut.get(-1) is None
    assert lut.get(-1, default="sentinel") == "sentinel"


def test_docstring_backed_get_returns_default_for_missing_docstring():
    lut = DocstringBackedExplanations(_FakeEnumType({7: _FakeEnumMember(None)}))
    assert lut.get(7) is None
    assert lut.get(7, default="sentinel") == "sentinel"


@pytest.mark.parametrize(
    ("version", "expected"),
    [
        pytest.param((12, 9, 5), False, id="before_12_9_6"),
        pytest.param((12, 9, 6), True, id="from_12_9_6"),
        pytest.param((13, 0, 0), False, id="13_0_mainline_gap"),
        pytest.param((13, 1, 1), False, id="13_1_1"),
        pytest.param((13, 2, 0), True, id="from_13_2_0"),
    ],
)
def test_binding_version_has_usable_enum_docstrings(version, expected):
    assert _binding_version_has_usable_enum_docstrings(version) is expected


@pytest.mark.parametrize(
    ("version", "expects_docstrings"),
    [
        pytest.param((12, 9, 5), False, id="before_12_9_6"),
        pytest.param((12, 9, 6), True, id="from_12_9_6"),
        pytest.param((13, 0, 0), False, id="13_0_mainline_gap"),
        pytest.param((13, 2, 0), True, id="from_13_2_0"),
    ],
)
def test_get_best_available_explanations_switches_by_version(monkeypatch, version, expects_docstrings):
    fallback = {7: "fallback text"}
    monkeypatch.setattr(enum_explanations_helpers, "_binding_version", lambda: version)
    expl = enum_explanations_helpers.get_best_available_explanations(
        _FakeEnumType({7: _FakeEnumMember("clean me")}),
        fallback,
    )
    if expects_docstrings:
        assert isinstance(expl, DocstringBackedExplanations)
        assert expl.get(7) == "clean me"
    else:
        assert expl is fallback


def test_get_best_available_explanations_calls_loader_before_docstrings(monkeypatch):
    fallback = {7: "fallback text"}
    calls = []

    def load_fallback():
        calls.append("loaded")
        return fallback

    monkeypatch.setattr(enum_explanations_helpers, "_binding_version", lambda: (13, 1, 1))
    expl = enum_explanations_helpers.get_best_available_explanations(
        _FakeEnumType({7: _FakeEnumMember("clean me")}),
        load_fallback,
    )
    assert expl is fallback
    assert calls == ["loaded"]


def test_driver_explanations_module_skips_fallback_import_when_docstrings_available(monkeypatch):
    import cuda.core._utils.driver_cu_result_explanations as driver_explanations

    monkeypatch.setattr(enum_explanations_helpers, "_binding_version", lambda: (13, 2, 0))
    sys.modules.pop("cuda.core._utils.driver_cu_result_explanations_frozen", None)

    importlib.reload(driver_explanations)

    assert "cuda.core._utils.driver_cu_result_explanations_frozen" not in sys.modules
    assert isinstance(driver_explanations.DRIVER_CU_RESULT_EXPLANATIONS, DocstringBackedExplanations)


def test_runtime_explanations_module_skips_fallback_import_when_docstrings_available(monkeypatch):
    import cuda.core._utils.runtime_cuda_error_explanations as runtime_explanations

    monkeypatch.setattr(enum_explanations_helpers, "_binding_version", lambda: (13, 2, 0))
    sys.modules.pop("cuda.core._utils.runtime_cuda_error_explanations_frozen", None)

    importlib.reload(runtime_explanations)

    assert "cuda.core._utils.runtime_cuda_error_explanations_frozen" not in sys.modules
    assert isinstance(runtime_explanations.RUNTIME_CUDA_ERROR_EXPLANATIONS, DocstringBackedExplanations)
