import time
from dataclasses import dataclass
from logging import Logger
from subprocess import PIPE, Popen
from typing import 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 (
    furmark_dependency,
)
from test_automation_suite.utility.mangohud_utility import (
    MangoHud,
)
from test_automation_suite.utility.nvidia_utility import Nvidia


@dataclass
class FurmarkConfig:
    """
    Configuration for FurMark benchmark execution.

    Values may be overridden by checks to customize duration,
    resolution, demo selection, or other benchmark parameters.

    NOTE: --duration and --max-time don't seem to be taken
    Also the preset --p1440 will leave random traces of the Furmark,
    making this Benchmark a one-hit-wonder at every boot. After a reboot it runs one time
    """

    demo: str = "furmark-vk"
    width: int = 2048
    height: int = 1440
    fullscreen: bool = False


@dataclass
class FurmarkStatistics:
    fps_avg: float
    fps_median: float
    fps_min: float
    fps_max: float
    fps_1_percent_low: float


class BenchmarkFurmark(Benchmark):
    benchmark_duration: int = 300
    furmark_config: FurmarkConfig = FurmarkConfig()

    @override
    def dependencies(self) -> list[Dependency]:
        return [*super().dependencies(), furmark_dependency, mangohud_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 = "furmark"

        # 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("Running Furmark Benchmarkt")
        process = BenchmarkFurmark.start_benchmark(self.furmark_config)

        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()

        nvidia_stats = Nvidia.calculate_statistics(csv_path)
        stats = Nvidia.get_nvidia_data()

        time.sleep(2)
        fps_stats = hud.calculate_fps_statistics()
        logger.debug(
            f"Current power draw: {stats.power_draw}, median power draw: {nvidia_stats.power_draw_median} and max power {stats.power_max}"
        )
        success = nvidia_stats.power_draw_median > stats.power_max * 0.9
        if not success:
            logger.error("Power draw was too low")

        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,
                "power_draw_1_percent_high": nvidia_stats.power_draw_1_percent_high,
                "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(config: FurmarkConfig) -> Popen[str]:
        return Popen(
            BenchmarkFurmark.build_command(config),
            env=Nvidia.nvidia_env(),
            cwd=furmark_dependency.installation_path,
            stdout=PIPE,
            stderr=PIPE,
            text=True,
            encoding="utf-8",
            errors="replace",
        )

    @staticmethod
    def build_command(config: FurmarkConfig) -> list[str]:
        exe = furmark_dependency.installation_path / "furmark"
        command = [
            "mangohud",
            str(exe),
            "--demo",
            config.demo,
            "--width",
            str(config.width),
            "--height",
            str(config.height),
        ]

        if config.fullscreen:
            command.append("--fullscreen")

        return command
