import time
from logging import Logger
from pathlib import Path
from subprocess import PIPE, Popen
from typing import ClassVar, override

from test_automation_suite.benchmarks.benchmark import Benchmark
from test_automation_suite.core.context import Context
from test_automation_suite.core.task import Step, StepResult
from test_automation_suite.dependencies.dependency import Dependency
from test_automation_suite.dependencies.init_apt_dependencies import mangohud_dependency
from test_automation_suite.dependencies.init_unpack_dependencies import gravitymark_dependency
from test_automation_suite.utility.mangohud_utility import (
    MangoHud,
)
from test_automation_suite.utility.nvidia_utility import Nvidia


class BenchmarkGravitymark(Benchmark):
    benchmark_duration: int = 3600

    exe_path: ClassVar[Path] = (
        gravitymark_dependency.installation_path
        / "GravityMark_1.89_linux"
        / "bin"
        / "GravityMark.x64"
    )

    @override
    def dependencies(self) -> list[Dependency]:
        return [*super().dependencies(), mangohud_dependency, gravitymark_dependency]

    @override
    def steps(self) -> list[Step]:
        return [*super().steps(), self.step_run]

    def step_run(self, context: Context, logger: Logger) -> StepResult:
        benchmark_name: str = "Gravitymark"

        # The benchmark depends on nvidia-smi for GPU monitoring, so end the check early if it is unavailable.
        try:
            Nvidia.get_nvidia_data()
        except RuntimeError as error:
            logger.error("%s", error)
            return StepResult(
                success=False,
                harvest_data={
                    "error": str(error),
                },
            )

        gpu_log_folder = context.output_directory / "gpu_log"
        gpu_log_folder.mkdir(parents=True, exist_ok=True)

        logger.info("Prepare MangoHUD")
        hud = MangoHud(gpu_log_folder / benchmark_name)

        logger.info(f"Prepare {benchmark_name}")
        process = BenchmarkGravitymark.start_benchmark()

        time.sleep(5)
        logger.info("Logging Data")
        csv_path = gpu_log_folder / f"{self.__class__.__name__}.csv"
        Nvidia.log_nvidia(csv_path, duration=self.benchmark_duration, process=process)

        process.terminate()

        logger.debug("NVIDIA CSV path: %s", csv_path)
        nvidia_stats = Nvidia.calculate_statistics(csv_path)
        stats = Nvidia.get_nvidia_data()

        fps_stats = hud.calculate_fps_statistics()
        success = fps_stats.fps_median > 0.0 and nvidia_stats.power_draw_median > 0.0
        if not success:
            logger.error(
                f"FPS statistics ({fps_stats.fps_median}) or power draw ({nvidia_stats.power_draw_median}) were 0"
            )

        return StepResult(
            success=success,
            harvest_data={
                "power_draw": stats.power_draw,
                "power_max": stats.power_max,
                "power_draw_median": nvidia_stats.power_draw_median,
                "power_draw_1_percent_low": nvidia_stats.power_draw_1_percent_low,
                "Average FPS": fps_stats.fps_avg,
                "Median FPS": fps_stats.fps_median,
                "Minimum FPS": fps_stats.fps_min,
                "Maximum FPS": fps_stats.fps_max,
                "1% Low FPS": fps_stats.fps_1_percent_low,
            },
        )

    @staticmethod
    def start_benchmark() -> Popen[str]:
        return Popen(
            BenchmarkGravitymark.build_command(),
            env=Nvidia.nvidia_env(),
            cwd=BenchmarkGravitymark.exe_path.parent,
            stdout=PIPE,
            stderr=PIPE,
            text=True,
            encoding="utf-8",
            errors="replace",
        )

    @staticmethod
    def build_command() -> list[str]:
        """
        Build the command used to launch GravityMark.

        Instead of using the provided ``run_windowed_vk.sh`` launcher script,
        the benchmark executable is started directly. This avoids random child processes
        and can be managed with subprocess.

        GravityMark arguments:
        - ``-vk``: Use the Vulkan renderer.
        - ``-temporal 1``: Enable temporal anti-aliasing.
        - ``-fps 0``: Hide the built-in FPS counter.
        - ``-info 0``: Hide benchmark information.
        - ``-sensors 0``: Hide hardware sensor overlay.
        - ``-benchmark 1``: Start the benchmark automatically.
        - ``-close 1``: Exit when the benchmark finishes.
        - ``-count 1``: Run a single benchmark pass.
        - ``-asteroids 1000000``: Render one million asteroids.
        """
        return [
            "mangohud",
            str(BenchmarkGravitymark.exe_path),
            "-vk",
            "-temporal",
            "1",
            "-fps",
            "0",
            "-info",
            "0",
            "-sensors",
            "0",
            "-benchmark",
            "1",
            "-close",
            "1",
            "-count",
            "1",
            "-asteroids",
            "1000000",
        ]
