#!/usr/bin/env python3
"""
AMD SMI Complete API Test Script
================================

PURPOSE
-------
Run a capability inventory of the AMD SMI (`amdsmi`) Python module against a
host. Each known API is invoked and classified into one of five buckets, then a
markdown report is emitted.

REQUIREMENTS
------------
* Linux host with ROCm/AMDSMI installed and the `amdgpu` kernel driver loaded
  (`/dev/kfd` must exist).
* Python 3.8+ with the `amdsmi` Python bindings importable. If multiple
  Python interpreters are installed (e.g. `python3` and `python3.9`), this
  script will auto-detect one that has `amdsmi` and re-execute itself with
  that interpreter, so you normally do NOT need to pick the right one
  yourself. Override by invoking the script directly with the interpreter
  you want.
* For full coverage of write/setter APIs, run the script as root:
      sudo python3 test_amdsmi_all_apis.py
  Without root, every API tagged as a "write" call is reported as
  `Skipped (requires root)` rather than failing as `Unsupported`.

STATUS CATEGORIES
-----------------
* Supported       The API exists, was called, and returned successfully.
* Unsupported     The API exists but the underlying driver/hardware rejected
                  the call, OR the installed amdsmi build expects a different
                  call signature than this script provides
                  (`signature mismatch:` prefix in the note).
* Skipped         The API was intentionally not executed. Reasons include:
                  risky write (e.g. GPU reset, partition change), requires
                  root and the script was run unprivileged, a required enum
                  member is not present in this amdsmi build, or no usable
                  prerequisite (e.g. no active GPU process) was available.
* Script Error    A genuine bug in this test script (any non-signature
                  AttributeError/TypeError). Should be 0 on a clean run.
* API Not Found   The symbol is not present in the installed amdsmi module.

USAGE
-----
  python3 test_amdsmi_all_apis.py                          # primary GPU only
  sudo python3 test_amdsmi_all_apis.py                     # include write APIs
  python3 test_amdsmi_all_apis.py --gpu-index 1            # target a specific GPU
  python3 test_amdsmi_all_apis.py --all-gpus               # primary sweep + per-GPU sanity sweep
  python3 test_amdsmi_all_apis.py --output ./report.md     # custom markdown report path
  python3 test_amdsmi_all_apis.py --skip-writes            # never run setters
  python3 test_amdsmi_all_apis.py --no-revert              # do not restore mutated state

OUTPUT
------
A timestamped markdown report is written next to this script
(`test_amdsmi_all_apis_output_YYYYMMDD_HHMMSS.md`) unless `--output` is
supplied. The report contains a summary table, a per-API status row, and the
script version that produced it.
"""

__version__ = "1.1.0"

import argparse
import datetime
import importlib.util
import os
import platform
import shutil
import subprocess
import sys


# ----------------------------------------------------------------------------
# Auto-detect a Python interpreter that has the amdsmi module installed.
# This must run BEFORE `import amdsmi`. If the current interpreter doesn't
# have amdsmi but another candidate does, re-execute this script with that
# interpreter so the user doesn't have to know which one to pick.
# ----------------------------------------------------------------------------
def _maybe_reexec_with_amdsmi():
    if importlib.util.find_spec("amdsmi") is not None:
        return  # current interpreter is fine
    # Allow callers to opt out (avoids accidental infinite re-exec loops).
    if os.environ.get("AMDSMI_NO_REEXEC") == "1":
        return

    candidates = [
        "python3",
        "python3.13",
        "python3.12",
        "python3.11",
        "python3.10",
        "python3.9",
        "python3.8",
        "/opt/rocm/bin/python3",
        "/usr/local/bin/python3",
    ]
    seen = {os.path.realpath(sys.executable)}
    for cand in candidates:
        path = cand if os.path.isabs(cand) else shutil.which(cand)
        if not path or not os.path.exists(path):
            continue
        real = os.path.realpath(path)
        if real in seen:
            continue
        seen.add(real)
        try:
            r = subprocess.run([path, "-c", "import amdsmi"], capture_output=True, timeout=5)
        except Exception:
            continue
        if r.returncode == 0:
            print(
                f"[INFO] amdsmi not importable in {sys.executable}; re-executing with {path}",
                flush=True,
            )
            env = dict(os.environ)
            env["AMDSMI_NO_REEXEC"] = "1"
            try:
                os.execve(path, [path] + sys.argv, env)
            except OSError:
                # Fall back to subprocess on platforms where execve is restricted.
                sys.exit(subprocess.call([path] + sys.argv, env=env))


_maybe_reexec_with_amdsmi()

try:
    import amdsmi
except ImportError as e:
    print(f"ERROR: Cannot import amdsmi: {e}")
    print("Tried this interpreter and any candidates found on PATH.")
    print("Install the amdsmi Python bindings (e.g. via the ROCm/amd-smi packages),")
    print("or invoke this script with the interpreter that already has them.")
    sys.exit(1)


def is_root():
    """Cross-platform root/admin check."""
    if hasattr(os, "geteuid"):
        try:
            return os.geteuid() == 0
        except Exception:
            return False
    # Windows: best-effort admin check
    try:
        import ctypes

        return bool(ctypes.windll.shell32.IsUserAnAdmin())
    except Exception:
        return False


def get_hostname():
    try:
        return platform.node() or "unknown"
    except Exception:
        return "unknown"


class _EnumMissing(Exception):
    """Raised inside a test lambda when a required amdsmi enum is not present."""

    pass


def need(attr):
    """Return the amdsmi attribute or raise _EnumMissing. Used inside test lambdas."""
    if not hasattr(amdsmi, attr):
        raise _EnumMissing(f"amdsmi.{attr} not present in this build")
    return getattr(amdsmi, attr)


# ============================================================================
# GLOBALS
# ============================================================================
results = []
gpu_handle = None
gpu_handles = []
socket_handles = []
cpu_socket_handles = []
cpu_core_handles = []

# Saved values for reverting
saved_values = {}

# Runtime configuration (set in main()).
CONFIG = {
    "gpu_index": 0,
    "output": None,
    "skip_writes": False,
    "no_revert": False,
    "all_gpus": False,
}

# ============================================================================
# HELPER FUNCTIONS
# ============================================================================


def add_result(api_name, status, output="", note=""):
    """Add a test result to the results list."""
    results.append(
        {
            "api": api_name,
            "status": status,
            "output": str(output)[:120] if output else "",
            "note": note,
        }
    )


def test_api(api_name, test_func, skip_reason=None, requires_root=False):
    """
    Test a single API and record the result.

    Parameters:
        api_name: Name of the API to test
        test_func: Lambda/function to call the API (None if skipped)
        skip_reason: If provided, API is not executed. Existence is still reported.
        requires_root: If True and not running as root, the API is skipped instead of
                       executed (to avoid mislabeling permission errors as Unsupported).
    """
    print(f"Testing: {api_name}... ", end="", flush=True)
    # Allow tagged names like "[gpu1] amdsmi_get_gpu_device_bdf" — strip the
    # tag for the existence check while keeping the tag in the report row.
    lookup_name = (
        api_name.split("] ", 1)[1] if api_name.startswith("[") and "] " in api_name else api_name
    )
    exists = hasattr(amdsmi, lookup_name)

    # Explicit skip path: preserve existence information separately from skip.
    if skip_reason:
        if exists:
            print(f"Skipped ({skip_reason})")
            add_result(api_name, "Skipped", note=f"API exists, skipped: {skip_reason}")
        else:
            print("API Not Found")
            add_result(api_name, "API Not Found", note=f"module amdsmi has no attribute {api_name}")
        return

    if not exists:
        print("API Not Found")
        add_result(api_name, "API Not Found", note=f"module amdsmi has no attribute {api_name}")
        return

    if requires_root:
        if CONFIG.get("skip_writes"):
            print("Skipped (--skip-writes)")
            add_result(api_name, "Skipped", note="write API skipped via --skip-writes")
            return
        if not is_root():
            print("Skipped (requires root)")
            add_result(api_name, "Skipped", note="write/privileged API; skipped when not root")
            return

    # Try to call the API
    try:
        result = test_func()
        print("Supported")
        add_result(api_name, "Supported", output=result)
    except _EnumMissing as e:
        print("Skipped (enum missing)")
        add_result(api_name, "Skipped", note=str(e)[:100])
    except (AttributeError, TypeError) as e:
        msg = str(e)
        # Signature mismatches indicate the installed amdsmi build expects
        # a different argument list than this script uses. That's a build
        # difference, not a script bug, so report as Unsupported.
        sig_markers = (
            "missing",
            "required positional",
            "takes",
            "arguments",
            "positional argument",
        )
        if any(m in msg for m in sig_markers):
            print("Unsupported")
            add_result(api_name, "Unsupported", note=f"signature mismatch: {msg[:90]}")
        else:
            # Genuine script bug.
            print("Script Error")
            add_result(api_name, "Script Error", note=msg[:100])
    except getattr(amdsmi, "AmdSmiLibraryException", Exception) as e:
        print("Unsupported")
        add_result(api_name, "Unsupported", note=str(e)[:100])
    except Exception as e:
        print("Unsupported")
        add_result(api_name, "Unsupported", note=str(e)[:100])


def safe_get(func, default=None):
    """Safely get a value, returning default on any error."""
    try:
        return func()
    except Exception:
        return default


# ============================================================================
# MAIN TEST RUNNER
# ============================================================================


def run_tests():
    global \
        gpu_handle, \
        gpu_handles, \
        socket_handles, \
        cpu_socket_handles, \
        cpu_core_handles, \
        saved_values

    # ========================================================================
    # INITIALIZATION
    # ========================================================================
    print("=" * 70)
    print(f"AMD SMI COMPLETE API TEST   (script v{__version__})")
    print("=" * 70)
    print()
    print("Initializing AMD SMI...")

    amdsmi.amdsmi_init()

    # Get all handles
    gpu_handles = amdsmi.amdsmi_get_processor_handles()
    if not gpu_handles:
        print("ERROR: No GPU handles found!")
        amdsmi.amdsmi_shut_down()
        sys.exit(1)

    gpu_index = CONFIG.get("gpu_index", 0)
    if gpu_index < 0 or gpu_index >= len(gpu_handles):
        print(f"ERROR: --gpu-index {gpu_index} out of range (found {len(gpu_handles)} GPUs).")
        amdsmi.amdsmi_shut_down()
        sys.exit(2)
    gpu_handle = gpu_handles[gpu_index]

    # Get socket handles
    try:
        socket_handles = amdsmi.amdsmi_get_socket_handles()
    except:
        socket_handles = []

    # Get CPU socket handles (for CPU APIs)
    try:
        cpu_socket_handles = amdsmi.amdsmi_get_cpu_handles()
    except:
        cpu_socket_handles = []

    # Get CPU core handles
    try:
        cpu_core_handles = amdsmi.amdsmi_get_cpucore_handles()
    except:
        cpu_core_handles = []

    # Get GPU info for display
    asic = amdsmi.amdsmi_get_gpu_asic_info(gpu_handle)
    gpu_name = asic.get("market_name", "Unknown")
    bdf = amdsmi.amdsmi_get_gpu_device_bdf(gpu_handle)

    print(f"GPU: {gpu_name}")
    print(f"BDF: {bdf}")
    print(f"GPUs found: {len(gpu_handles)}")
    print(f"Sockets found: {len(socket_handles)}")
    print(f"CPU Sockets found: {len(cpu_socket_handles)}")
    print(f"CPU Cores found: {len(cpu_core_handles)}")
    print(f"Running as: {'ROOT' if is_root() else 'USER'}")
    print("=" * 70)
    print()

    # ========================================================================
    # SAVE CURRENT VALUES FOR REVERTING
    # ========================================================================
    print("Saving current values for revert...")

    saved_values["power_cap"] = safe_get(
        lambda: amdsmi.amdsmi_get_power_cap_info(gpu_handle).get("power_cap")
    )
    saved_values["perf_level"] = safe_get(lambda: amdsmi.amdsmi_get_gpu_perf_level(gpu_handle))
    saved_values["fan_speed"] = safe_get(lambda: amdsmi.amdsmi_get_gpu_fan_speed(gpu_handle, 0))
    saved_values["process_isolation"] = safe_get(
        lambda: amdsmi.amdsmi_get_gpu_process_isolation(gpu_handle)
    )

    print(f"  Power Cap: {saved_values['power_cap']}")
    print(f"  Perf Level: {saved_values['perf_level']}")
    print(f"  Fan Speed: {saved_values['fan_speed']}")
    print(f"  Process Isolation: {saved_values['process_isolation']}")
    print()

    # ========================================================================
    # SECTION 1: INITIALIZATION & SHUTDOWN (2 APIs)
    # ========================================================================
    print("=" * 70)
    print("SECTION 1: INITIALIZATION & SHUTDOWN (2 APIs)")
    print("=" * 70)
    print()

    # amdsmi_init - already called, just verify it exists
    test_api("amdsmi_init", None, skip_reason="Already called during setup")

    # amdsmi_shut_down - will call at end
    test_api("amdsmi_shut_down", None, skip_reason="Will call at end of tests")

    # ========================================================================
    # SECTION 2: PROCESSOR/DEVICE DISCOVERY (14 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 2: PROCESSOR/DEVICE DISCOVERY (14 APIs)")
    print("=" * 70)
    print()

    test_api("amdsmi_get_processor_type", lambda: amdsmi.amdsmi_get_processor_type(gpu_handle))

    test_api("amdsmi_get_processor_handles", lambda: amdsmi.amdsmi_get_processor_handles())

    test_api(
        "amdsmi_get_processor_handles_by_type",
        lambda: (
            amdsmi.amdsmi_get_processor_handles_by_type(
                socket_handles[0],
                getattr(
                    need("AmdSmiProcessorType"),
                    "AMD_GPU",
                    getattr(need("AmdSmiProcessorType"), "AMDSMI_PROCESSOR_TYPE_AMD_GPU", 0),
                ),
            )
            if socket_handles
            else (_ for _ in ()).throw(_EnumMissing("no socket handles available"))
        ),
    )

    test_api(
        "amdsmi_get_processor_count_from_handles",
        lambda: amdsmi.amdsmi_get_processor_count_from_handles(gpu_handles),
    )

    test_api(
        "amdsmi_get_processor_handle_from_bdf",
        lambda: amdsmi.amdsmi_get_processor_handle_from_bdf(bdf),
    )

    test_api("amdsmi_get_processor_info", lambda: amdsmi.amdsmi_get_processor_info(gpu_handle))

    test_api("amdsmi_get_socket_handles", lambda: amdsmi.amdsmi_get_socket_handles())

    test_api(
        "amdsmi_get_socket_info",
        lambda: amdsmi.amdsmi_get_socket_info(socket_handles[0]) if socket_handles else None,
    )

    test_api("amdsmi_get_node_handle", lambda: amdsmi.amdsmi_get_node_handle(gpu_handle))

    test_api("amdsmi_get_gpu_device_bdf", lambda: amdsmi.amdsmi_get_gpu_device_bdf(gpu_handle))

    test_api("amdsmi_get_gpu_device_uuid", lambda: amdsmi.amdsmi_get_gpu_device_uuid(gpu_handle))

    test_api("amdsmi_get_gpu_bdf_id", lambda: amdsmi.amdsmi_get_gpu_bdf_id(gpu_handle))

    test_api(
        "amdsmi_get_gpu_enumeration_info",
        lambda: amdsmi.amdsmi_get_gpu_enumeration_info(gpu_handle),
    )

    test_api("amdsmi_get_gpu_kfd_info", lambda: amdsmi.amdsmi_get_gpu_kfd_info(gpu_handle))

    # ========================================================================
    # SECTION 3: DRIVER & VERSION INFO (8 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 3: DRIVER & VERSION INFO (8 APIs)")
    print("=" * 70)
    print()

    test_api(
        "amdsmi_get_gpu_driver_version", lambda: amdsmi.amdsmi_get_gpu_driver_version(gpu_handle)
    )

    test_api("amdsmi_get_gpu_driver_info", lambda: amdsmi.amdsmi_get_gpu_driver_info(gpu_handle))

    test_api("amdsmi_get_version", lambda: amdsmi.amdsmi_get_version())

    test_api("amdsmi_get_version_str", lambda: amdsmi.amdsmi_get_version_str())

    test_api("amdsmi_get_lib_version", lambda: amdsmi.amdsmi_get_lib_version())

    test_api("amdsmi_get_rocm_version", lambda: amdsmi.amdsmi_get_rocm_version())

    test_api("amdsmi_status_string", lambda: amdsmi.amdsmi_status_string(0))

    test_api("amdsmi_status_code_to_string", lambda: amdsmi.amdsmi_status_code_to_string(0))

    # ========================================================================
    # SECTION 4: GPU HARDWARE INFO (12 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 4: GPU HARDWARE INFO (12 APIs)")
    print("=" * 70)
    print()

    test_api("amdsmi_get_gpu_asic_info", lambda: amdsmi.amdsmi_get_gpu_asic_info(gpu_handle))

    test_api("amdsmi_get_gpu_vram_info", lambda: amdsmi.amdsmi_get_gpu_vram_info(gpu_handle))

    test_api("amdsmi_get_gpu_board_info", lambda: amdsmi.amdsmi_get_gpu_board_info(gpu_handle))

    test_api("amdsmi_get_gpu_vbios_info", lambda: amdsmi.amdsmi_get_gpu_vbios_info(gpu_handle))

    test_api("amdsmi_get_gpu_cache_info", lambda: amdsmi.amdsmi_get_gpu_cache_info(gpu_handle))

    test_api("amdsmi_get_gpu_revision", lambda: amdsmi.amdsmi_get_gpu_revision(gpu_handle))

    test_api("amdsmi_get_gpu_vendor_name", lambda: amdsmi.amdsmi_get_gpu_vendor_name(gpu_handle))

    test_api("amdsmi_get_gpu_id", lambda: amdsmi.amdsmi_get_gpu_id(gpu_handle))

    test_api(
        "amdsmi_get_gpu_drm_render_minor",
        lambda: amdsmi.amdsmi_get_gpu_drm_render_minor(gpu_handle),
    )

    test_api("amdsmi_get_gpu_subsystem_id", lambda: amdsmi.amdsmi_get_gpu_subsystem_id(gpu_handle))

    test_api(
        "amdsmi_get_gpu_subsystem_name", lambda: amdsmi.amdsmi_get_gpu_subsystem_name(gpu_handle)
    )

    # ========================================================================
    # SECTION 5: FIRMWARE INFO (2 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 5: FIRMWARE INFO (2 APIs)")
    print("=" * 70)
    print()

    test_api("amdsmi_get_fw_info", lambda: amdsmi.amdsmi_get_fw_info(gpu_handle))

    test_api(
        "amdsmi_get_npm_info",
        lambda: (
            amdsmi.amdsmi_get_npm_info(amdsmi.amdsmi_get_node_handle(gpu_handle))
            if hasattr(amdsmi, "amdsmi_get_node_handle")
            else None
        ),
    )

    # ========================================================================
    # SECTION 6: POWER MANAGEMENT (10 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 6: POWER MANAGEMENT (10 APIs)")
    print("=" * 70)
    print()

    test_api("amdsmi_get_power_cap_info", lambda: amdsmi.amdsmi_get_power_cap_info(gpu_handle))

    test_api(
        "amdsmi_get_supported_power_cap", lambda: amdsmi.amdsmi_get_supported_power_cap(gpu_handle)
    )

    test_api("amdsmi_get_power_info", lambda: amdsmi.amdsmi_get_power_info(gpu_handle))

    # Set power cap - use current value (safe)
    if saved_values["power_cap"]:
        test_api(
            "amdsmi_set_power_cap",
            lambda: amdsmi.amdsmi_set_power_cap(gpu_handle, 0, saved_values["power_cap"]),
            requires_root=True,
        )
    else:
        test_api("amdsmi_set_power_cap", None, skip_reason="Could not get current power cap")

    test_api(
        "amdsmi_get_gpu_power_profile_presets",
        lambda: amdsmi.amdsmi_get_gpu_power_profile_presets(gpu_handle, 0),
    )

    test_api(
        "amdsmi_set_gpu_power_profile",
        lambda: amdsmi.amdsmi_set_gpu_power_profile(
            gpu_handle, 0, need("AmdSmiPowerProfilePresetMasks").BOOTUP_DEFAULT
        ),
        requires_root=True,
    )

    test_api(
        "amdsmi_is_gpu_power_management_enabled",
        lambda: amdsmi.amdsmi_is_gpu_power_management_enabled(gpu_handle),
    )

    test_api("amdsmi_get_energy_count", lambda: amdsmi.amdsmi_get_energy_count(gpu_handle))

    test_api("amdsmi_get_violation_status", lambda: amdsmi.amdsmi_get_violation_status(gpu_handle))

    # ========================================================================
    # SECTION 7: PCIe INFO (12 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 7: PCIe INFO (12 APIs)")
    print("=" * 70)
    print()

    test_api("amdsmi_get_pcie_info", lambda: amdsmi.amdsmi_get_pcie_info(gpu_handle))

    test_api("amdsmi_get_pcie_link_status", lambda: amdsmi.amdsmi_get_pcie_link_status(gpu_handle))

    test_api("amdsmi_get_pcie_link_caps", lambda: amdsmi.amdsmi_get_pcie_link_caps(gpu_handle))

    test_api("amdsmi_get_gpu_pci_id", lambda: amdsmi.amdsmi_get_gpu_pci_id(gpu_handle))

    test_api(
        "amdsmi_get_gpu_pci_bandwidth", lambda: amdsmi.amdsmi_get_gpu_pci_bandwidth(gpu_handle)
    )

    test_api(
        "amdsmi_set_gpu_pci_bandwidth",
        None,
        skip_reason="Risky - writing arbitrary PCIe bandwidth mask is hardware-dependent",
    )

    test_api(
        "amdsmi_get_gpu_pci_throughput", lambda: amdsmi.amdsmi_get_gpu_pci_throughput(gpu_handle)
    )

    test_api(
        "amdsmi_get_gpu_pci_replay_counter",
        lambda: amdsmi.amdsmi_get_gpu_pci_replay_counter(gpu_handle),
    )

    test_api("amdsmi_get_gpu_ptl_formats", lambda: amdsmi.amdsmi_get_gpu_ptl_formats(gpu_handle))

    test_api("amdsmi_get_gpu_ptl_state", lambda: amdsmi.amdsmi_get_gpu_ptl_state(gpu_handle))

    test_api("amdsmi_set_gpu_ptl_formats", None, skip_reason="Risky - modifies PCIe PTL formats")

    test_api("amdsmi_set_gpu_ptl_state", None, skip_reason="Risky - modifies PCIe PTL state")

    # ========================================================================
    # SECTION 8: ACTIVITY & UTILIZATION (6 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 8: ACTIVITY & UTILIZATION (6 APIs)")
    print("=" * 70)
    print()

    test_api("amdsmi_get_gpu_activity", lambda: amdsmi.amdsmi_get_gpu_activity(gpu_handle))

    test_api("amdsmi_get_busy_percent", lambda: amdsmi.amdsmi_get_busy_percent(gpu_handle))

    test_api("amdsmi_get_gpu_busy_percent", lambda: amdsmi.amdsmi_get_gpu_busy_percent(gpu_handle))

    # Build a portable counter_types list from whatever this build exposes.
    def _utilization_counter_types():
        ut = need("AmdSmiUtilizationCounterType")
        candidates = [
            "COARSE_GRAIN_GFX_ACTIVITY",
            "COARSE_GRAIN_MEM_ACTIVITY",
            "FINE_GRAIN_GFX_ACTIVITY",
            "FINE_GRAIN_MEM_ACTIVITY",
        ]
        picked = [getattr(ut, name) for name in candidates if hasattr(ut, name)]
        if not picked:
            raise _EnumMissing("no AmdSmiUtilizationCounterType members available")
        return picked

    test_api(
        "amdsmi_get_utilization_count",
        lambda: amdsmi.amdsmi_get_utilization_count(gpu_handle, _utilization_counter_types()),
    )

    test_api("amdsmi_get_gpu_xcd_counter", lambda: amdsmi.amdsmi_get_gpu_xcd_counter(gpu_handle))

    test_api("amdsmi_clean_gpu_local_data", None, skip_reason="Risky - could delete GPU local data")

    # ========================================================================
    # SECTION 9: CLOCK & FREQUENCY (15 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 9: CLOCK & FREQUENCY (15 APIs)")
    print("=" * 70)
    print()

    test_api(
        "amdsmi_get_clock_info",
        lambda: amdsmi.amdsmi_get_clock_info(gpu_handle, need("AmdSmiClkType").GFX),
    )

    test_api(
        "amdsmi_get_clk_freq",
        lambda: amdsmi.amdsmi_get_clk_freq(gpu_handle, need("AmdSmiClkType").GFX),
    )

    test_api(
        "amdsmi_set_clk_freq",
        None,
        skip_reason="Risky - writing arbitrary clk freq mask is hardware-dependent",
    )

    test_api(
        "amdsmi_get_gpu_target_frequency_range",
        lambda: amdsmi.amdsmi_get_gpu_target_frequency_range(gpu_handle),
    )

    # set_gpu_clk_limit takes (handle, clk_type, limit_type, value).
    # Read the current target range and replay max as a no-op-ish write.
    def _set_clk_limit_noop():
        clk_type = need("AmdSmiClkType").GFX
        limit_type_enum = need("AmdSmiClkLimitType")
        # Prefer SOFT_MAX, fall back to MAX or first member.
        limit_type = getattr(
            limit_type_enum, "SOFT_MAX", getattr(limit_type_enum, "MAX", list(limit_type_enum)[0])
        )
        cur = (
            safe_get(lambda: amdsmi.amdsmi_get_gpu_target_frequency_range(gpu_handle), default={})
            or {}
        )
        # target_frequency_range usually returns dict with min/max per clk
        max_clk = None
        if isinstance(cur, dict):
            for k in ("max_clk", "max", "sclk_max", "gfx_max"):
                if k in cur:
                    max_clk = cur[k]
                    break
        if not max_clk:
            max_clk = 1500  # sane fallback in MHz
        return amdsmi.amdsmi_set_gpu_clk_limit(gpu_handle, clk_type, limit_type, int(max_clk))

    test_api("amdsmi_set_gpu_clk_limit", _set_clk_limit_noop, requires_root=True)

    test_api("amdsmi_get_gpu_od_clk_info", lambda: amdsmi.amdsmi_get_gpu_od_clk_info(gpu_handle))

    test_api(
        "amdsmi_set_gpu_od_clk_info",
        lambda: amdsmi.amdsmi_set_gpu_od_clk_info(
            gpu_handle, need("AmdSmiFreqInd").MIN, 500, need("AmdSmiClkType").GFX
        ),
        requires_root=True,
    )

    test_api("amdsmi_get_gpu_od_volt_info", lambda: amdsmi.amdsmi_get_gpu_od_volt_info(gpu_handle))

    test_api(
        "amdsmi_set_gpu_od_volt_info",
        lambda: amdsmi.amdsmi_set_gpu_od_volt_info(gpu_handle, 0, 500, 700),
        requires_root=True,
    )

    # od_volt_curve_regions needs num_regions; pull it from od_volt_info.
    def _od_volt_curve_regions():
        info = safe_get(lambda: amdsmi.amdsmi_get_gpu_od_volt_info(gpu_handle), default={}) or {}
        n = 0
        if isinstance(info, dict):
            n = int(info.get("num_regions") or info.get("num_curve_regions") or 0)
        if n <= 0:
            n = 1  # try a single region; API will error cleanly if unsupported
        return amdsmi.amdsmi_get_gpu_od_volt_curve_regions(gpu_handle, n)

    test_api("amdsmi_get_gpu_od_volt_curve_regions", _od_volt_curve_regions)

    test_api("amdsmi_get_dfc_ctrl", lambda: amdsmi.amdsmi_get_dfc_ctrl(gpu_handle))

    test_api("amdsmi_set_dfc_ctrl", None, skip_reason="Risky - modifies Data Fabric Clock control")

    test_api("amdsmi_get_soc_pstate", lambda: amdsmi.amdsmi_get_soc_pstate(gpu_handle))

    test_api(
        "amdsmi_set_soc_pstate",
        lambda: amdsmi.amdsmi_set_soc_pstate(gpu_handle, 0),
        requires_root=True,
    )

    # ========================================================================
    # SECTION 10: PERFORMANCE LEVEL & OVERDRIVE (8 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 10: PERFORMANCE LEVEL & OVERDRIVE (8 APIs)")
    print("=" * 70)
    print()

    test_api("amdsmi_get_gpu_perf_level", lambda: amdsmi.amdsmi_get_gpu_perf_level(gpu_handle))

    test_api(
        "amdsmi_set_gpu_perf_level",
        lambda: amdsmi.amdsmi_set_gpu_perf_level(gpu_handle, need("AmdSmiDevPerfLevel").AUTO),
        requires_root=True,
    )

    test_api(
        "amdsmi_set_gpu_perf_level_v1",
        lambda: amdsmi.amdsmi_set_gpu_perf_level_v1(gpu_handle, need("AmdSmiDevPerfLevel").AUTO),
        requires_root=True,
    )

    test_api(
        "amdsmi_set_gpu_perf_determinism_mode",
        lambda: amdsmi.amdsmi_set_gpu_perf_determinism_mode(gpu_handle, 1000),
        requires_root=True,
    )

    test_api(
        "amdsmi_get_gpu_overdrive_level", lambda: amdsmi.amdsmi_get_gpu_overdrive_level(gpu_handle)
    )

    test_api(
        "amdsmi_set_gpu_overdrive_level",
        lambda: amdsmi.amdsmi_set_gpu_overdrive_level(gpu_handle, 0),
        requires_root=True,
    )

    test_api(
        "amdsmi_set_gpu_overdrive_level_v1",
        lambda: amdsmi.amdsmi_set_gpu_overdrive_level_v1(gpu_handle, 0),
        requires_root=True,
    )

    test_api(
        "amdsmi_get_gpu_mem_overdrive_level",
        lambda: amdsmi.amdsmi_get_gpu_mem_overdrive_level(gpu_handle),
    )

    # ========================================================================
    # SECTION 11: TEMPERATURE & VOLTAGE & METRICS (7 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 11: TEMPERATURE & VOLTAGE & METRICS (7 APIs)")
    print("=" * 70)
    print()

    test_api(
        "amdsmi_get_temp_metric",
        lambda: amdsmi.amdsmi_get_temp_metric(
            gpu_handle, need("AmdSmiTemperatureType").EDGE, need("AmdSmiTemperatureMetric").CURRENT
        ),
    )

    test_api(
        "amdsmi_get_gpu_volt_metric",
        lambda: amdsmi.amdsmi_get_gpu_volt_metric(
            gpu_handle, need("AmdSmiVoltageType").VDDGFX, need("AmdSmiVoltageMetric").CURRENT
        ),
    )

    test_api("amdsmi_get_gpu_metrics_info", lambda: amdsmi.amdsmi_get_gpu_metrics_info(gpu_handle))

    test_api(
        "amdsmi_get_gpu_metrics_header_info",
        lambda: amdsmi.amdsmi_get_gpu_metrics_header_info(gpu_handle),
    )

    test_api(
        "amdsmi_get_gpu_pm_metrics_info", lambda: amdsmi.amdsmi_get_gpu_pm_metrics_info(gpu_handle)
    )

    test_api(
        "amdsmi_get_gpu_partition_metrics_info",
        lambda: amdsmi.amdsmi_get_gpu_partition_metrics_info(gpu_handle),
    )

    test_api(
        "amdsmi_get_gpu_reg_table_info", lambda: amdsmi.amdsmi_get_gpu_reg_table_info(gpu_handle, 0)
    )

    # ========================================================================
    # SECTION 12: FAN CONTROL (5 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 12: FAN CONTROL (5 APIs)")
    print("=" * 70)
    print()

    test_api("amdsmi_get_gpu_fan_rpms", lambda: amdsmi.amdsmi_get_gpu_fan_rpms(gpu_handle, 0))

    test_api("amdsmi_get_gpu_fan_speed", lambda: amdsmi.amdsmi_get_gpu_fan_speed(gpu_handle, 0))

    test_api(
        "amdsmi_get_gpu_fan_speed_max", lambda: amdsmi.amdsmi_get_gpu_fan_speed_max(gpu_handle, 0)
    )

    test_api(
        "amdsmi_set_gpu_fan_speed",
        lambda: amdsmi.amdsmi_set_gpu_fan_speed(gpu_handle, 0, 128),
        requires_root=True,
    )

    test_api(
        "amdsmi_reset_gpu_fan",
        lambda: amdsmi.amdsmi_reset_gpu_fan(gpu_handle, 0),
        requires_root=True,
    )

    # ========================================================================
    # SECTION 13: MEMORY (8 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 13: MEMORY (8 APIs)")
    print("=" * 70)
    print()

    test_api("amdsmi_get_gpu_vram_usage", lambda: amdsmi.amdsmi_get_gpu_vram_usage(gpu_handle))

    test_api(
        "amdsmi_get_gpu_memory_total",
        lambda: amdsmi.amdsmi_get_gpu_memory_total(gpu_handle, need("AmdSmiMemoryType").VRAM),
    )

    test_api(
        "amdsmi_get_gpu_memory_usage",
        lambda: amdsmi.amdsmi_get_gpu_memory_usage(gpu_handle, need("AmdSmiMemoryType").VRAM),
    )

    test_api(
        "amdsmi_get_gpu_memory_busy_percent",
        lambda: amdsmi.amdsmi_get_gpu_memory_busy_percent(gpu_handle),
    )

    test_api(
        "amdsmi_get_gpu_memory_reserved_pages",
        lambda: amdsmi.amdsmi_get_gpu_memory_reserved_pages(gpu_handle),
    )

    test_api(
        "amdsmi_get_gpu_bad_page_info", lambda: amdsmi.amdsmi_get_gpu_bad_page_info(gpu_handle)
    )

    test_api(
        "amdsmi_get_gpu_bad_page_threshold",
        lambda: amdsmi.amdsmi_get_gpu_bad_page_threshold(gpu_handle),
    )

    # ========================================================================
    # SECTION 14: RAS & ECC (10 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 14: RAS & ECC (10 APIs)")
    print("=" * 70)
    print()

    test_api(
        "amdsmi_get_gpu_ras_block_features_enabled",
        lambda: amdsmi.amdsmi_get_gpu_ras_block_features_enabled(gpu_handle),
    )

    test_api(
        "amdsmi_get_gpu_ras_feature_info",
        lambda: amdsmi.amdsmi_get_gpu_ras_feature_info(gpu_handle),
    )

    test_api(
        "amdsmi_get_gpu_ecc_error_count", lambda: amdsmi.amdsmi_get_gpu_ecc_error_count(gpu_handle)
    )

    test_api(
        "amdsmi_get_gpu_total_ecc_count", lambda: amdsmi.amdsmi_get_gpu_total_ecc_count(gpu_handle)
    )

    test_api(
        "amdsmi_get_gpu_ecc_count",
        lambda: amdsmi.amdsmi_get_gpu_ecc_count(gpu_handle, need("AmdSmiGpuBlock").UMC),
    )

    test_api("amdsmi_get_gpu_ecc_enabled", lambda: amdsmi.amdsmi_get_gpu_ecc_enabled(gpu_handle))

    test_api(
        "amdsmi_get_gpu_ecc_status",
        lambda: amdsmi.amdsmi_get_gpu_ecc_status(gpu_handle, need("AmdSmiGpuBlock").UMC),
    )

    test_api(
        "amdsmi_get_gpu_cper_entries", lambda: amdsmi.amdsmi_get_gpu_cper_entries(gpu_handle, 0)
    )

    test_api("amdsmi_get_afids_from_cper", lambda: amdsmi.amdsmi_get_afids_from_cper(gpu_handle))

    test_api(
        "amdsmi_gpu_validate_ras_eeprom", lambda: amdsmi.amdsmi_gpu_validate_ras_eeprom(gpu_handle)
    )

    # ========================================================================
    # SECTION 15: PROCESS MANAGEMENT (7 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 15: PROCESS MANAGEMENT (7 APIs)")
    print("=" * 70)
    print()

    test_api("amdsmi_get_gpu_process_list", lambda: amdsmi.amdsmi_get_gpu_process_list(gpu_handle))

    # Pick a real PID from the process list if possible; otherwise skip.
    _proc_list = safe_get(lambda: amdsmi.amdsmi_get_gpu_process_list(gpu_handle), default=[])
    _first_pid = None
    if _proc_list:
        _p0 = _proc_list[0]
        _first_pid = _p0.get("pid") if isinstance(_p0, dict) else _p0
    if _first_pid:
        test_api(
            "amdsmi_get_gpu_process_info",
            lambda: amdsmi.amdsmi_get_gpu_process_info(gpu_handle, _first_pid),
        )
    else:
        test_api(
            "amdsmi_get_gpu_process_info", None, skip_reason="No active GPU processes to query"
        )

    test_api(
        "amdsmi_get_gpu_compute_process_info", lambda: amdsmi.amdsmi_get_gpu_compute_process_info()
    )

    test_api(
        "amdsmi_get_gpu_compute_process_info_by_pid",
        lambda: amdsmi.amdsmi_get_gpu_compute_process_info_by_pid(os.getpid()),
    )

    test_api(
        "amdsmi_get_gpu_compute_process_gpus",
        lambda: amdsmi.amdsmi_get_gpu_compute_process_gpus(os.getpid()),
    )

    test_api(
        "amdsmi_get_gpu_process_isolation",
        lambda: amdsmi.amdsmi_get_gpu_process_isolation(gpu_handle),
    )

    # Set process isolation - use saved value
    if saved_values["process_isolation"] is not None:
        test_api(
            "amdsmi_set_gpu_process_isolation",
            lambda: amdsmi.amdsmi_set_gpu_process_isolation(
                gpu_handle, saved_values["process_isolation"]
            ),
            requires_root=True,
        )
    else:
        test_api(
            "amdsmi_set_gpu_process_isolation",
            lambda: amdsmi.amdsmi_set_gpu_process_isolation(gpu_handle, 0),
            requires_root=True,
        )

    # ========================================================================
    # SECTION 16: TOPOLOGY & NUMA (11 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 16: TOPOLOGY & NUMA (11 APIs)")
    print("=" * 70)
    print()

    test_api(
        "amdsmi_get_gpu_topo_numa_affinity",
        lambda: amdsmi.amdsmi_get_gpu_topo_numa_affinity(gpu_handle),
    )

    test_api(
        "amdsmi_topo_get_numa_node_number",
        lambda: amdsmi.amdsmi_topo_get_numa_node_number(gpu_handle),
    )

    test_api(
        "amdsmi_topo_get_link_weight",
        lambda: (
            amdsmi.amdsmi_topo_get_link_weight(gpu_handles[0], gpu_handles[1])
            if len(gpu_handles) >= 2
            else amdsmi.amdsmi_topo_get_link_weight(gpu_handle, gpu_handle)
        ),
    )

    test_api(
        "amdsmi_topo_get_link_type",
        lambda: (
            amdsmi.amdsmi_topo_get_link_type(gpu_handles[0], gpu_handles[1])
            if len(gpu_handles) >= 2
            else amdsmi.amdsmi_topo_get_link_type(gpu_handle, gpu_handle)
        ),
    )

    test_api(
        "amdsmi_topo_get_p2p_status",
        lambda: (
            amdsmi.amdsmi_topo_get_p2p_status(gpu_handles[0], gpu_handles[1])
            if len(gpu_handles) >= 2
            else amdsmi.amdsmi_topo_get_p2p_status(gpu_handle, gpu_handle)
        ),
    )

    test_api(
        "amdsmi_get_P2P_status",
        lambda: (
            amdsmi.amdsmi_get_P2P_status(gpu_handles[0], gpu_handles[1])
            if len(gpu_handles) >= 2
            else amdsmi.amdsmi_get_P2P_status(gpu_handle, gpu_handle)
        ),
    )

    test_api(
        "amdsmi_is_P2P_accessible",
        lambda: (
            amdsmi.amdsmi_is_P2P_accessible(gpu_handles[0], gpu_handles[1])
            if len(gpu_handles) >= 2
            else amdsmi.amdsmi_is_P2P_accessible(gpu_handle, gpu_handle)
        ),
    )

    test_api(
        "amdsmi_get_minmax_bandwidth",
        lambda: (
            amdsmi.amdsmi_get_minmax_bandwidth(gpu_handles[0], gpu_handles[1])
            if len(gpu_handles) >= 2
            else amdsmi.amdsmi_get_minmax_bandwidth(gpu_handle, gpu_handle)
        ),
    )

    test_api(
        "amdsmi_get_minmax_bandwidth_between_processors",
        lambda: (
            amdsmi.amdsmi_get_minmax_bandwidth_between_processors(gpu_handles[0], gpu_handles[1])
            if len(gpu_handles) >= 2
            else amdsmi.amdsmi_get_minmax_bandwidth_between_processors(gpu_handle, gpu_handle)
        ),
    )

    # Some amdsmi builds raise AttributeError internally (e.g. missing
    # 'domain_number' on union_amdsmi_bdf_t). Re-raise as a runtime error
    # so it reports as Unsupported rather than a Script Error.
    def _get_link_metrics():
        try:
            return amdsmi.amdsmi_get_link_metrics(gpu_handle)
        except AttributeError as e:
            raise RuntimeError(f"amdsmi internal: {e}")

    test_api("amdsmi_get_link_metrics", _get_link_metrics)

    # link_topology_nearest needs a link_type; try common members across builds.
    def _link_topology_nearest():
        lt = need("AmdSmiLinkType")
        for name in ("XGMI", "PCIE", "AMDSMI_LINK_TYPE_XGMI", "AMDSMI_LINK_TYPE_PCIE"):
            if hasattr(lt, name):
                return amdsmi.amdsmi_get_link_topology_nearest(gpu_handle, getattr(lt, name))
        # Fall back to first enum value
        first = list(lt)[0]
        return amdsmi.amdsmi_get_link_topology_nearest(gpu_handle, first)

    test_api("amdsmi_get_link_topology_nearest", _link_topology_nearest)

    # ========================================================================
    # SECTION 17: XGMI (6 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 17: XGMI (6 APIs)")
    print("=" * 70)
    print()

    test_api("amdsmi_get_xgmi_info", lambda: amdsmi.amdsmi_get_xgmi_info(gpu_handle))

    test_api("amdsmi_get_xgmi_plpd", lambda: amdsmi.amdsmi_get_xgmi_plpd(gpu_handle))

    test_api(
        "amdsmi_set_xgmi_plpd",
        lambda: amdsmi.amdsmi_set_xgmi_plpd(gpu_handle, 0),
        requires_root=True,
    )

    test_api(
        "amdsmi_get_gpu_xgmi_link_status",
        lambda: amdsmi.amdsmi_get_gpu_xgmi_link_status(gpu_handle),
    )

    test_api(
        "amdsmi_gpu_xgmi_error_status", lambda: amdsmi.amdsmi_gpu_xgmi_error_status(gpu_handle)
    )

    test_api(
        "amdsmi_reset_gpu_xgmi_error",
        lambda: amdsmi.amdsmi_reset_gpu_xgmi_error(gpu_handle),
        requires_root=True,
    )

    # ========================================================================
    # SECTION 18: PARTITIONING (15 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 18: PARTITIONING (15 APIs)")
    print("=" * 70)
    print()

    test_api(
        "amdsmi_get_gpu_compute_partition",
        lambda: amdsmi.amdsmi_get_gpu_compute_partition(gpu_handle),
    )

    test_api(
        "amdsmi_set_gpu_compute_partition",
        None,
        skip_reason="Risky - changes compute partition, may need reboot",
    )

    test_api(
        "amdsmi_dev_compute_partition_get",
        lambda: amdsmi.amdsmi_dev_compute_partition_get(gpu_handle),
    )

    test_api(
        "amdsmi_dev_compute_partition_set",
        None,
        skip_reason="Risky - changes compute partition, may need reboot",
    )

    test_api(
        "amdsmi_dev_compute_partition_reset", None, skip_reason="Risky - resets compute partition"
    )

    test_api(
        "amdsmi_get_gpu_memory_partition",
        lambda: amdsmi.amdsmi_get_gpu_memory_partition(gpu_handle),
    )

    # amdsmi_set_gpu_memory_partition deprecated, use amdsmi_set_gpu_memory_partition_mode instead
    test_api(
        "amdsmi_set_gpu_memory_partition",
        None,
        skip_reason="Risky - changes memory partition, may need reboot",
    )

    test_api(
        "amdsmi_set_gpu_memory_partition_mode",
        None,
        skip_reason="Risky - changes memory partition mode",
    )

    test_api(
        "amdsmi_get_gpu_memory_partition_config",
        lambda: amdsmi.amdsmi_get_gpu_memory_partition_config(gpu_handle),
    )

    test_api(
        "amdsmi_get_gpu_accelerator_partition_profile",
        lambda: amdsmi.amdsmi_get_gpu_accelerator_partition_profile(gpu_handle),
    )

    test_api(
        "amdsmi_set_gpu_accelerator_partition_profile",
        None,
        skip_reason="Risky - changes accelerator partition profile",
    )

    test_api(
        "amdsmi_get_gpu_accelerator_partition_profile_config",
        lambda: amdsmi.amdsmi_get_gpu_accelerator_partition_profile_config(gpu_handle),
    )

    test_api("amdsmi_dev_nps_mode_get", lambda: amdsmi.amdsmi_dev_nps_mode_get(gpu_handle))

    test_api("amdsmi_dev_nps_mode_set", None, skip_reason="Risky - changes NPS mode")

    test_api("amdsmi_dev_nps_mode_reset", None, skip_reason="Risky - resets NPS mode")

    # ========================================================================
    # SECTION 19: VIRTUALIZATION (2 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 19: VIRTUALIZATION (2 APIs)")
    print("=" * 70)
    print()

    test_api(
        "amdsmi_get_gpu_virtualization_mode",
        lambda: amdsmi.amdsmi_get_gpu_virtualization_mode(gpu_handle),
    )

    # cpu_affinity_with_scope needs (gpu_handle, scope).
    def _cpu_affinity_with_scope():
        scope_enum = need("AmdSmiCpuAffinityScope")
        scope = getattr(scope_enum, "NODE", getattr(scope_enum, "SOCKET", list(scope_enum)[0]))
        return amdsmi.amdsmi_get_cpu_affinity_with_scope(gpu_handle, scope)

    test_api("amdsmi_get_cpu_affinity_with_scope", _cpu_affinity_with_scope)

    # ========================================================================
    # SECTION 20: EVENT NOTIFICATION (5 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 20: EVENT NOTIFICATION (5 APIs)")
    print("=" * 70)
    print()

    # AmdSmiEventReader is a class; just verify it is importable.
    if hasattr(amdsmi, "AmdSmiEventReader"):
        add_result("AmdSmiEventReader", "Supported", output="class present")
        print("Testing: AmdSmiEventReader... Supported")
    else:
        add_result(
            "AmdSmiEventReader",
            "API Not Found",
            note="module amdsmi has no attribute AmdSmiEventReader",
        )
        print("Testing: AmdSmiEventReader... API Not Found")

    test_api(
        "amdsmi_init_gpu_event_notification",
        lambda: amdsmi.amdsmi_init_gpu_event_notification(gpu_handle),
    )

    test_api(
        "amdsmi_set_gpu_event_notification_mask",
        None,
        skip_reason="Risky - modifies event notification state",
    )

    test_api(
        "amdsmi_get_gpu_event_notification",
        None,
        skip_reason="Requires an active event session with a configured mask",
    )

    test_api(
        "amdsmi_stop_gpu_event_notification",
        lambda: amdsmi.amdsmi_stop_gpu_event_notification(gpu_handle),
    )

    # ========================================================================
    # SECTION 21: PERFORMANCE COUNTERS (6 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 21: PERFORMANCE COUNTERS (6 APIs)")
    print("=" * 70)
    print()

    test_api(
        "amdsmi_gpu_counter_group_supported",
        lambda: amdsmi.amdsmi_gpu_counter_group_supported(
            gpu_handle, need("AmdSmiEventGroup").XGMI
        ),
    )

    test_api(
        "amdsmi_gpu_create_counter",
        lambda: amdsmi.amdsmi_gpu_create_counter(gpu_handle, need("AmdSmiEventType").XGMI_0_NOP_TX),
    )

    test_api(
        "amdsmi_gpu_destroy_counter", None, skip_reason="Requires valid counter handle from create"
    )

    test_api(
        "amdsmi_gpu_control_counter", None, skip_reason="Requires valid counter handle from create"
    )

    test_api(
        "amdsmi_gpu_read_counter", None, skip_reason="Requires valid counter handle from create"
    )

    test_api(
        "amdsmi_get_gpu_available_counters",
        lambda: amdsmi.amdsmi_get_gpu_available_counters(gpu_handle, need("AmdSmiEventGroup").XGMI),
    )

    # ========================================================================
    # SECTION 22: ITERATOR APIs (5 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 22: ITERATOR APIs (5 APIs)")
    print("=" * 70)
    print()

    test_api(
        "amdsmi_open_supported_func_iterator",
        lambda: amdsmi.amdsmi_open_supported_func_iterator(gpu_handle),
    )

    test_api(
        "amdsmi_open_supported_variant_iterator",
        lambda: amdsmi.amdsmi_open_supported_variant_iterator(gpu_handle),
    )

    test_api(
        "amdsmi_close_supported_func_iterator", None, skip_reason="Requires valid iterator handle"
    )

    test_api("amdsmi_next_func_iter", None, skip_reason="Requires valid iterator handle")

    test_api("amdsmi_get_func_iter_value", None, skip_reason="Requires valid iterator handle")

    # ========================================================================
    # SECTION 23: RESET APIs (1 API)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 23: RESET APIs (1 API)")
    print("=" * 70)
    print()

    test_api("amdsmi_reset_gpu", None, skip_reason="Risky - would reset GPU, could crash system")

    # ========================================================================
    # SECTION 24: CPU/HSMP APIs (74 APIs)
    # ========================================================================
    print()
    print("=" * 70)
    print("SECTION 24: CPU/HSMP APIs (74 APIs)")
    print("=" * 70)
    print()

    # Get CPU socket handle for CPU APIs
    cpu_socket = cpu_socket_handles[0] if cpu_socket_handles else None
    cpu_core = cpu_core_handles[0] if cpu_core_handles else None

    test_api("amdsmi_get_cpu_handles", lambda: amdsmi.amdsmi_get_cpu_handles())

    test_api("amdsmi_get_cpucore_handles", lambda: amdsmi.amdsmi_get_cpucore_handles())

    test_api(
        "amdsmi_get_cpu_hsmp_proto_ver",
        lambda: amdsmi.amdsmi_get_cpu_hsmp_proto_ver(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_smu_fw_version",
        lambda: amdsmi.amdsmi_get_cpu_smu_fw_version(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_hsmp_driver_version",
        lambda: amdsmi.amdsmi_get_cpu_hsmp_driver_version(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_core_energy",
        lambda: amdsmi.amdsmi_get_cpu_core_energy(cpu_core) if cpu_core else None,
    )

    test_api(
        "amdsmi_get_cpu_core_ccd_power",
        lambda: amdsmi.amdsmi_get_cpu_core_ccd_power(cpu_core) if cpu_core else None,
    )

    test_api(
        "amdsmi_get_cpu_socket_energy",
        lambda: amdsmi.amdsmi_get_cpu_socket_energy(cpu_socket) if cpu_socket else None,
    )

    test_api("amdsmi_get_threads_per_core", lambda: amdsmi.amdsmi_get_threads_per_core())

    test_api(
        "amdsmi_get_cpu_prochot_status",
        lambda: amdsmi.amdsmi_get_cpu_prochot_status(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_fclk_mclk",
        lambda: amdsmi.amdsmi_get_cpu_fclk_mclk(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_cclk_limit",
        lambda: amdsmi.amdsmi_get_cpu_cclk_limit(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_socket_current_active_freq_limit",
        lambda: (
            amdsmi.amdsmi_get_cpu_socket_current_active_freq_limit(cpu_socket)
            if cpu_socket
            else None
        ),
    )

    test_api(
        "amdsmi_get_cpu_socket_freq_range",
        lambda: amdsmi.amdsmi_get_cpu_socket_freq_range(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_core_current_freq_limit",
        lambda: amdsmi.amdsmi_get_cpu_core_current_freq_limit(cpu_core) if cpu_core else None,
    )

    test_api(
        "amdsmi_get_cpu_socket_power",
        lambda: amdsmi.amdsmi_get_cpu_socket_power(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_socket_power_cap",
        lambda: amdsmi.amdsmi_get_cpu_socket_power_cap(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_socket_power_cap_max",
        lambda: amdsmi.amdsmi_get_cpu_socket_power_cap_max(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_pwr_svi_telemetry_all_rails",
        lambda: (
            amdsmi.amdsmi_get_cpu_pwr_svi_telemetry_all_rails(cpu_socket) if cpu_socket else None
        ),
    )

    test_api("amdsmi_set_cpu_socket_power_cap", None, skip_reason="Risky - modifies CPU power cap")

    test_api(
        "amdsmi_set_cpu_pwr_efficiency_mode",
        None,
        skip_reason="Risky - modifies CPU power efficiency mode",
    )

    test_api(
        "amdsmi_get_cpu_pwr_efficiency_mode",
        lambda: amdsmi.amdsmi_get_cpu_pwr_efficiency_mode(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_core_boostlimit",
        lambda: amdsmi.amdsmi_get_cpu_core_boostlimit(cpu_core) if cpu_core else None,
    )

    test_api(
        "amdsmi_get_cpu_socket_c0_residency",
        lambda: amdsmi.amdsmi_get_cpu_socket_c0_residency(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_set_cpu_core_boostlimit", None, skip_reason="Risky - modifies CPU core boost limit"
    )

    test_api(
        "amdsmi_set_cpu_socket_boostlimit",
        None,
        skip_reason="Risky - modifies CPU socket boost limit",
    )

    test_api(
        "amdsmi_get_cpu_ddr_bw",
        lambda: amdsmi.amdsmi_get_cpu_ddr_bw(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_socket_temperature",
        lambda: amdsmi.amdsmi_get_cpu_socket_temperature(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_dimm_temp_range_and_refresh_rate",
        lambda: (
            amdsmi.amdsmi_get_cpu_dimm_temp_range_and_refresh_rate(cpu_socket, 0)
            if cpu_socket
            else None
        ),
    )

    test_api(
        "amdsmi_get_cpu_dimm_power_consumption",
        lambda: amdsmi.amdsmi_get_cpu_dimm_power_consumption(cpu_socket, 0) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_dimm_thermal_sensor",
        lambda: amdsmi.amdsmi_get_cpu_dimm_thermal_sensor(cpu_socket, 0) if cpu_socket else None,
    )

    test_api("amdsmi_set_cpu_xgmi_width", None, skip_reason="Risky - modifies CPU XGMI width")

    test_api(
        "amdsmi_set_cpu_gmi3_link_width_range", None, skip_reason="Risky - modifies GMI3 link width"
    )

    test_api(
        "amdsmi_cpu_apb_enable",
        lambda: amdsmi.amdsmi_cpu_apb_enable(cpu_socket) if cpu_socket else None,
    )

    test_api("amdsmi_cpu_apb_disable", None, skip_reason="Risky - disables CPU APB")

    test_api(
        "amdsmi_set_cpu_socket_lclk_dpm_level", None, skip_reason="Risky - modifies LCLK DPM level"
    )

    test_api(
        "amdsmi_get_cpu_socket_lclk_dpm_level",
        lambda: amdsmi.amdsmi_get_cpu_socket_lclk_dpm_level(cpu_socket, 0) if cpu_socket else None,
    )

    test_api("amdsmi_set_cpu_pcie_link_rate", None, skip_reason="Risky - modifies PCIe link rate")

    test_api(
        "amdsmi_set_cpu_df_pstate_range", None, skip_reason="Risky - modifies DF P-state range"
    )

    test_api(
        "amdsmi_get_cpu_current_io_bandwidth",
        lambda: amdsmi.amdsmi_get_cpu_current_io_bandwidth(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_current_xgmi_bw",
        lambda: amdsmi.amdsmi_get_cpu_current_xgmi_bw(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_hsmp_metrics_table_version",
        lambda: amdsmi.amdsmi_get_hsmp_metrics_table_version(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_set_cpu_xgmi_pstate_range", None, skip_reason="Risky - modifies XGMI P-state range"
    )

    test_api(
        "amdsmi_get_cpu_xgmi_pstate_range",
        lambda: amdsmi.amdsmi_get_cpu_xgmi_pstate_range(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_set_cpu_rail_isofreq_policy",
        None,
        skip_reason="Risky - modifies rail isofreq policy",
    )

    test_api(
        "amdsmi_get_cpu_rail_isofreq_policy",
        lambda: amdsmi.amdsmi_get_cpu_rail_isofreq_policy(cpu_socket) if cpu_socket else None,
    )

    test_api("amdsmi_set_cpu_dfc_ctrl", None, skip_reason="Risky - modifies CPU DFC control")

    test_api(
        "amdsmi_get_cpu_dfc_ctrl",
        lambda: amdsmi.amdsmi_get_cpu_dfc_ctrl(cpu_socket) if cpu_socket else None,
    )

    test_api("amdsmi_set_cpu_pc6_enable", None, skip_reason="Risky - modifies CPU PC6 state")

    test_api(
        "amdsmi_get_cpu_pc6_enable",
        lambda: amdsmi.amdsmi_get_cpu_pc6_enable(cpu_socket) if cpu_socket else None,
    )

    test_api("amdsmi_set_cpu_cc6_enable", None, skip_reason="Risky - modifies CPU CC6 state")

    test_api(
        "amdsmi_get_cpu_cc6_enable",
        lambda: amdsmi.amdsmi_get_cpu_cc6_enable(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_dimm_sb_reg",
        lambda: amdsmi.amdsmi_get_cpu_dimm_sb_reg(cpu_socket, 0, 0) if cpu_socket else None,
    )

    test_api(
        "amdsmi_set_cpu_dimm_sb_reg", None, skip_reason="Risky - modifies DIMM sideband registers"
    )

    test_api(
        "amdsmi_get_cpu_tdelta",
        lambda: amdsmi.amdsmi_get_cpu_tdelta(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_svi3_vr_controller_temp",
        lambda: amdsmi.amdsmi_get_cpu_svi3_vr_controller_temp(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_enabled_commands",
        lambda: amdsmi.amdsmi_get_cpu_enabled_commands(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_core_floor_freq_limit",
        lambda: amdsmi.amdsmi_get_cpu_core_floor_freq_limit(cpu_core) if cpu_core else None,
    )

    test_api(
        "amdsmi_get_cpu_floor_freq_limit",
        lambda: amdsmi.amdsmi_get_cpu_floor_freq_limit(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_cpu_core_eff_floor_freq_limit",
        lambda: amdsmi.amdsmi_get_cpu_core_eff_floor_freq_limit(cpu_core) if cpu_core else None,
    )

    test_api(
        "amdsmi_get_cpu_eff_floor_freq_limit",
        lambda: amdsmi.amdsmi_get_cpu_eff_floor_freq_limit(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_set_cpu_core_floor_freq_limit",
        None,
        skip_reason="Risky - modifies CPU core floor frequency",
    )

    test_api(
        "amdsmi_set_cpu_floor_freq_limit", None, skip_reason="Risky - modifies CPU floor frequency"
    )

    test_api(
        "amdsmi_set_cpu_msr_floor_freq_limit",
        None,
        skip_reason="Risky - modifies CPU MSR floor frequency",
    )

    test_api(
        "amdsmi_set_cpu_core_msr_floor_freq_limit",
        None,
        skip_reason="Risky - modifies CPU core MSR floor frequency",
    )

    test_api(
        "amdsmi_get_cpu_freq_range",
        lambda: amdsmi.amdsmi_get_cpu_freq_range(cpu_socket) if cpu_socket else None,
    )

    test_api("amdsmi_set_cpu_sdps_limit", None, skip_reason="Risky - modifies CPU SDPS limit")

    test_api(
        "amdsmi_get_cpu_sdps_limit",
        lambda: amdsmi.amdsmi_get_cpu_sdps_limit(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_get_hsmp_metrics_table",
        lambda: amdsmi.amdsmi_get_hsmp_metrics_table(cpu_socket) if cpu_socket else None,
    )

    test_api(
        "amdsmi_first_online_core_on_cpu_socket",
        lambda: amdsmi.amdsmi_first_online_core_on_cpu_socket(cpu_socket) if cpu_socket else None,
    )

    test_api("amdsmi_get_cpu_family", lambda: amdsmi.amdsmi_get_cpu_family())

    test_api("amdsmi_get_cpu_model", lambda: amdsmi.amdsmi_get_cpu_model())

    test_api(
        "amdsmi_get_cpu_model_name",
        lambda: amdsmi.amdsmi_get_cpu_model_name(cpu_socket) if cpu_socket else None,
    )

    test_api("amdsmi_get_esmi_err_msg", lambda: amdsmi.amdsmi_get_esmi_err_msg(0))

    # ========================================================================
    # MULTI-GPU SANITY SWEEP (only when --all-gpus is used)
    # ========================================================================
    if CONFIG.get("all_gpus") and len(gpu_handles) > 1:
        print()
        print("=" * 70)
        print(f"MULTI-GPU SANITY SWEEP ({len(gpu_handles) - 1} additional GPU(s))")
        print("=" * 70)
        print()

        sanity_apis = [
            ("amdsmi_get_gpu_device_bdf", lambda h: amdsmi.amdsmi_get_gpu_device_bdf(h)),
            ("amdsmi_get_gpu_device_uuid", lambda h: amdsmi.amdsmi_get_gpu_device_uuid(h)),
            ("amdsmi_get_gpu_asic_info", lambda h: amdsmi.amdsmi_get_gpu_asic_info(h)),
            ("amdsmi_get_gpu_board_info", lambda h: amdsmi.amdsmi_get_gpu_board_info(h)),
            ("amdsmi_get_gpu_vram_usage", lambda h: amdsmi.amdsmi_get_gpu_vram_usage(h)),
            ("amdsmi_get_gpu_activity", lambda h: amdsmi.amdsmi_get_gpu_activity(h)),
            ("amdsmi_get_power_info", lambda h: amdsmi.amdsmi_get_power_info(h)),
            (
                "amdsmi_get_clock_info",
                lambda h: amdsmi.amdsmi_get_clock_info(h, need("AmdSmiClkType").GFX),
            ),
            (
                "amdsmi_get_temp_metric",
                lambda h: amdsmi.amdsmi_get_temp_metric(
                    h, need("AmdSmiTemperatureType").EDGE, need("AmdSmiTemperatureMetric").CURRENT
                ),
            ),
        ]

        primary_index = CONFIG.get("gpu_index", 0)
        for idx, h in enumerate(gpu_handles):
            if idx == primary_index:
                continue
            for api_name, fn in sanity_apis:
                tagged = f"[gpu{idx}] {api_name}"
                test_api(tagged, lambda fn=fn, h=h: fn(h))

    # ========================================================================
    # REVERT SECTION
    # ========================================================================
    print()
    print("=" * 70)
    print("REVERTING WRITE OPERATIONS TO SAFE DEFAULTS")
    print("=" * 70)
    print()

    if CONFIG.get("no_revert"):
        print("  [INFO] --no-revert supplied; leaving any mutated state as-is.")
    elif CONFIG.get("skip_writes"):
        print("  [INFO] --skip-writes supplied; no setters ran, nothing to revert.")
    elif not is_root():
        print("  [INFO] Running as non-root user - write operations were skipped.")
        print("  [INFO] No changes were made, nothing to revert.")
    else:
        # Revert perf level to AUTO. Some builds return a string from the
        # getter that the setter won't accept, so always use the enum here.
        try:
            if hasattr(amdsmi, "AmdSmiDevPerfLevel"):
                amdsmi.amdsmi_set_gpu_perf_level(gpu_handle, amdsmi.AmdSmiDevPerfLevel.AUTO)
                print("  Reverted perf level to AUTO")
        except Exception as e:
            print(f"  Could not revert perf level: {str(e).splitlines()[0]}")

        # Revert power cap to saved value.
        try:
            if saved_values.get("power_cap"):
                amdsmi.amdsmi_set_power_cap(gpu_handle, 0, saved_values["power_cap"])
                print(f"  Reverted power_cap to {saved_values['power_cap']}")
        except Exception as e:
            print(f"  Could not revert power_cap: {str(e).splitlines()[0]}")

        # Revert fan to automatic; ignore NOT_SUPPORTED quietly.
        try:
            amdsmi.amdsmi_reset_gpu_fan(gpu_handle, 0)
            print("  Reverted fan to automatic")
        except Exception as e:
            msg = str(e)
            if "NOT_SUPPORTED" in msg:
                print("  Fan revert skipped (no controllable fan)")
            else:
                print(f"  Could not revert fan: {msg.splitlines()[0]}")

        # Revert process isolation
        if saved_values["process_isolation"] is not None:
            try:
                amdsmi.amdsmi_set_gpu_process_isolation(
                    gpu_handle, saved_values["process_isolation"]
                )
                print(f"  Reverted process_isolation to {saved_values['process_isolation']}")
            except Exception as e:
                print(f"  Could not revert process_isolation: {str(e).splitlines()[0]}")

    # Shutdown
    amdsmi.amdsmi_shut_down()
    print("  AMD SMI shut down successfully")

    # Generate report
    generate_report(gpu_name, bdf)


# ============================================================================
# REPORT GENERATION
# ============================================================================


def generate_report(gpu_name, bdf):
    """Generate a markdown report of all test results."""
    run_as = "ROOT" if is_root() else "USER"
    supported = [r for r in results if r["status"] == "Supported"]
    unsupported = [r for r in results if r["status"] == "Unsupported"]
    not_found = [r for r in results if r["status"] == "API Not Found"]
    skipped = [r for r in results if r["status"] == "Skipped"]
    script_errors = [r for r in results if r["status"] == "Script Error"]

    if CONFIG.get("output"):
        outfile = os.path.abspath(CONFIG["output"])
    else:
        ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        outfile = os.path.join(
            os.path.dirname(os.path.abspath(__file__)), f"test_amdsmi_all_apis_output_{ts}.md"
        )

    try:
        os.makedirs(os.path.dirname(outfile), exist_ok=True)
    except Exception:
        pass

    try:
        f_out = open(outfile, "w", encoding="utf-8")
    except OSError as e:
        print(f"WARNING: Cannot write report to {outfile}: {e}")
        print("Report not written. Final summary below.")
        print(
            f"FINAL SUMMARY: {len(supported)} Supported / "
            f"{len(unsupported)} Unsupported / {len(skipped)} Skipped / "
            f"{len(script_errors)} Script Errors / {len(not_found)} Not Found / "
            f"{len(results)} Total"
        )
        return

    with f_out as f:
        f.write(f"# AMD SMI Complete API Test Results\n\n")
        f.write(f"**Script Version:** {__version__}  \n")
        f.write(f"**Test Date:** {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}  \n")
        f.write(f"**System:** {gpu_name}  \n")
        f.write(f"**BDF:** {bdf}  \n")
        f.write(f"**Primary GPU Index:** {CONFIG.get('gpu_index', 0)}  \n")
        f.write(f"**GPUs:** {len(gpu_handles)}  \n")
        f.write(f"**Sockets:** {len(socket_handles)}  \n")
        f.write(f"**CPU Sockets:** {len(cpu_socket_handles)}  \n")
        f.write(f"**CPU Cores:** {len(cpu_core_handles)}  \n")
        f.write(f"**Platform:** Bare Metal  \n")
        f.write(f"**Run As:** {run_as}  \n")
        f.write(f"**Python:** {sys.executable} ({platform.python_version()})  \n")
        f.write(f"**amdsmi module:** {getattr(amdsmi, '__file__', 'unknown')}  \n")
        f.write(f"**Hostname:** {get_hostname()}  \n\n")
        f.write("---\n\n")

        # Summary
        f.write("## Summary\n\n")
        f.write("| Metric | Count |\n")
        f.write("|--------|-------|\n")
        f.write(f"| Total APIs Tested | {len(results)} |\n")
        f.write(f"| Supported | {len(supported)} |\n")
        f.write(f"| Unsupported | {len(unsupported)} |\n")
        f.write(f"| Skipped | {len(skipped)} |\n")
        f.write(f"| Script Error | {len(script_errors)} |\n")
        f.write(f"| API Not Found | {len(not_found)} |\n\n")
        f.write("---\n\n")

        # Complete results table
        f.write("## Complete Results\n\n")
        f.write("| # | API | Status | Output / Note |\n")
        f.write("|---|-----|--------|---------------|\n")
        for i, r in enumerate(results, 1):
            detail = r["output"] if r["output"] else r["note"]
            detail = detail.replace("|", "/").replace("\n", " ")[:100]
            f.write(f"| {i} | `{r['api']}` | {r['status']} | {detail} |\n")

        f.write("\n---\n\n")

        # Unsupported APIs detail
        if unsupported:
            f.write("## ❌ Unsupported APIs Detail\n\n")
            for r in unsupported:
                note = r["note"].replace("\n", " ")
                f.write(f"- **`{r['api']}`**: {note[:120]}\n")
            f.write("\n")

        # APIs Not Found
        if not_found:
            f.write("## ❓ APIs Not Found\n\n")
            for r in not_found:
                note = r["note"].replace("\n", " ")
                f.write(f"- **`{r['api']}`**: {note[:120]}\n")
            f.write("\n")

        # Supported APIs list
        f.write("## ✅ Supported APIs\n\n")
        for r in supported:
            f.write(f"- `{r['api']}`\n")
        f.write("\n")

    print()
    print(f"Report written to: {outfile}")
    print()
    print("=" * 70)
    print(
        f"FINAL SUMMARY: {len(supported)} Supported / "
        f"{len(unsupported)} Unsupported / {len(skipped)} Skipped / "
        f"{len(script_errors)} Script Errors / {len(not_found)} Not Found / "
        f"{len(results)} Total"
    )
    print("=" * 70)


# ============================================================================
# MAIN ENTRY POINT
# ============================================================================


def main():
    parser = argparse.ArgumentParser(
        description="Test AMD SMI Python APIs and emit a markdown report."
    )
    parser.add_argument(
        "--gpu-index", type=int, default=0, help="Index of the GPU to target (default: 0)."
    )
    parser.add_argument(
        "--output",
        default=None,
        help="Path to the markdown report (default: timestamped file next to this script).",
    )
    parser.add_argument(
        "--skip-writes",
        action="store_true",
        help="Skip every API marked requires_root (no setters will run).",
    )
    parser.add_argument(
        "--no-revert",
        action="store_true",
        help="Do not attempt to restore mutated state at the end.",
    )
    parser.add_argument(
        "--all-gpus",
        action="store_true",
        help="After the primary sweep, run a small read-only sanity "
        "sweep on every other GPU so multi-GPU systems are also "
        "validated. The primary sweep still runs only on --gpu-index.",
    )
    parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
    args = parser.parse_args()

    CONFIG["gpu_index"] = args.gpu_index
    CONFIG["output"] = args.output
    CONFIG["skip_writes"] = args.skip_writes
    CONFIG["no_revert"] = args.no_revert
    CONFIG["all_gpus"] = args.all_gpus

    run_tests()


if __name__ == "__main__":
    main()
