# Copyright (c) Advanced Micro Devices, Inc.
# SPDX-License-Identifier:  MIT

import pandas as pd
import pytest
import test_utils

config = {}
config["memcopy"] = ["tests/memcopy"]
config["cleanup"] = True

soc = test_utils.gpu_soc()

# workload -> gfx -> metric definition
VALIDATE_METRICS = {
    "memcopy": {
        "MI200": [
            {
                "name": "HBM Bandwidth",
                "metric_id": "4.1.8",
                "csv_file": "4.1_Roofline_Performance_Rates.csv",
                "column": "Value",
                "expected_values": [1389.17],
            },
        ],
        "MI300": [
            {
                "name": "HBM Bandwidth",
                "metric_id": "4.1.9",
                "csv_file": "4.1_Roofline_Performance_Rates.csv",
                "column": "Value",
                # MI 300 series contains MI 325X GPU which
                # uses improved HBM3E instead of HBM3 used in
                # MI 300X GPU. Hence, multiple expected values
                # to cover both cases.
                # MI 300A CPX and MI 308 have lower bandwidth.
                # MI 300X: 3910.62 GB/s
                # MI 325X: 4287.31 GB/s
                # MI 308: 2003.45 GB/s
                # MI 300A CPX: 710.23 GB/s (per-partition, CPX/NPS1)
                "expected_values": [710.23, 2003.45, 3910.62, 4287.31],
            },
        ],
        "MI350": [
            {
                "name": "HBM Bandwidth",
                "metric_id": "4.1.10",
                "csv_file": "4.1_Roofline_Performance_Rates.csv",
                "column": "Value",
                "expected_values": [5690.42],
            },
        ],
        # Ignore warmup dispatch
        # Collect roofline block
        "profile_options": ["-d", "2-1001", "-b", "4"],
        "roof": True,
    }
}


@pytest.mark.path
def test_validate_metrics(
    binary_handler_profile_rocprof_compute, binary_handler_analyze_rocprof_compute
):
    for workload in VALIDATE_METRICS.keys():
        metrics = VALIDATE_METRICS[workload].get(soc, [])
        metric_ids = [metric["metric_id"] for metric in metrics]
        if not metric_ids:
            print(
                f"Skipping metric validation for {workload} on {soc}. "
                "No metrics to validate."
            )
            continue

        profile_workload_dir = test_utils.get_output_dir(param_id=f"{workload}_profile")
        analysis_workload_dir = test_utils.get_output_dir(
            param_id=f"{workload}_analysis"
        )
        try:
            # Ensure non zero length of profile df
            options = VALIDATE_METRICS[workload].get("profile_options", [])
            _ = binary_handler_profile_rocprof_compute(
                config,
                profile_workload_dir,
                options,
                check_success=True,
                roof=VALIDATE_METRICS[workload].get("roof", False),
                app_name=workload,
            )
            _ = test_utils.check_csv_files(
                profile_workload_dir, num_devices=1, num_kernels=1
            )

            # Check whether metric values are correct
            code = binary_handler_analyze_rocprof_compute([
                "analyze",
                "--output-name",
                f"{analysis_workload_dir}",
                "--output-format",
                "csv",
                "-b",
                *metric_ids,
                "--path",
                profile_workload_dir,
            ])
            assert code == 0

            for metric in metrics:
                actual = pd.read_csv(f"{analysis_workload_dir}/{metric['csv_file']}")[
                    metric["column"]
                ].values[0]
                expected_values = metric["expected_values"]
                # 5% tolerance in checking - assert if actual matches any expected value
                matches = [
                    abs(actual - expected) / expected <= 0.05
                    for expected in expected_values
                ]
                diffs = [(abs(actual - exp) / exp * 100) for exp in expected_values]
                assert any(matches), (
                    f"{metric['name']} ({metric['metric_id']}): "
                    f"actual={actual}, expected_values={expected_values}, "
                    f"diffs={diffs} (tolerance: 5%)"
                )
        finally:
            test_utils.clean_output_dir(config["cleanup"], analysis_workload_dir)
            test_utils.clean_output_dir(config["cleanup"], profile_workload_dir)
