import plistlib
import subprocess
from pathlib import Path
from typing import Any

import aitop.core.gpu.apple as apple_module
from aitop.core.gpu.apple import AppleGPUMonitor
from aitop.core.gpu.base import GPUInfo


def _build_system_profiler_payload(items: list[dict[str, Any]]) -> bytes:
    return plistlib.dumps([{"_dataType": "SPDisplaysDataType", "_items": items}])


def _gpu_stub(index: int = 0) -> GPUInfo:
    return GPUInfo(
        index=index,
        name=f"Apple GPU {index}",
        utilization=0.0,
        memory_used=0.0,
        memory_total=8192.0,
        temperature=0.0,
        power_draw=0.0,
        power_limit=0.0,
        processes=[],
    )


def test_parse_memory_mb_converts_common_units() -> None:
    assert AppleGPUMonitor._parse_memory_mb("8 GB") == 8192.0
    assert AppleGPUMonitor._parse_memory_mb("1536 MB") == 1536.0
    assert AppleGPUMonitor._parse_memory_mb("1,536 MB") == 1536.0
    assert AppleGPUMonitor._parse_memory_mb("8 GiB") == 8192.0
    assert AppleGPUMonitor._parse_memory_mb("1,048,576 bytes") == 1.0
    assert round(AppleGPUMonitor._parse_memory_mb("1024 KB"), 4) == 1.0


def test_get_gpu_info_parses_system_profiler_xml(monkeypatch) -> None:
    payload = _build_system_profiler_payload(
        [
            {
                "sppci_model": "Apple M4",
                "spdisplays_vendor": "Apple",
                "spdisplays_vram_shared": "12 GB",
                "spdisplays_metal": "Metal 3",
            }
        ]
    )

    def _fake_run(
        cmd: list[str], *args: Any, **kwargs: Any
    ) -> subprocess.CompletedProcess[bytes]:
        del args, kwargs
        return subprocess.CompletedProcess(cmd, 0, payload, b"")

    monitor = AppleGPUMonitor()
    monitor._smi_path = Path("/usr/sbin/system_profiler")
    monitor._smi_path_initialized = True
    monkeypatch.setattr(apple_module.subprocess, "run", _fake_run)

    gpus = monitor.get_gpu_info()

    assert len(gpus) == 1
    assert gpus[0].name == "Apple M4"
    assert gpus[0].memory_total == 12288.0
    assert gpus[0].utilization == 0.0
    assert gpus[0].processes == []


def test_get_gpu_info_uses_cache_to_limit_system_profiler_calls(monkeypatch) -> None:
    payload = _build_system_profiler_payload(
        [{"sppci_model": "Apple M3", "spdisplays_vram_shared": "8 GB"}]
    )
    call_count = {"value": 0}

    def _fake_run(
        cmd: list[str], *args: Any, **kwargs: Any
    ) -> subprocess.CompletedProcess[bytes]:
        del args, kwargs
        call_count["value"] += 1
        return subprocess.CompletedProcess(cmd, 0, payload, b"")

    monitor = AppleGPUMonitor(cache_ttl_seconds=120.0)
    monitor._smi_path = Path("/usr/sbin/system_profiler")
    monitor._smi_path_initialized = True
    monkeypatch.setattr(apple_module.subprocess, "run", _fake_run)

    first = monitor.get_gpu_info()
    second = monitor.get_gpu_info()

    assert len(first) == 1
    assert len(second) == 1
    assert first[0].name == second[0].name
    assert call_count["value"] == 1


def test_parse_powermetrics_utilization_reads_active_residency() -> None:
    output = """
    CPU Power notify
    GPU HW active residency: 42.7%
    """
    parsed = AppleGPUMonitor._parse_powermetrics_utilization(output)
    assert parsed == 42.7


def test_parse_powermetrics_utilization_ignores_idle_residency() -> None:
    output = """
    GPU HW active residency: 12.4%
    GPU HW idle residency: 87.6%
    """
    parsed = AppleGPUMonitor._parse_powermetrics_utilization(output)
    assert parsed == 12.4


def test_get_gpu_info_parses_underscore_vram_key(monkeypatch) -> None:
    payload = _build_system_profiler_payload(
        [
            {
                "sppci_model": "Apple M3",
                "spdisplays_vendor": "Apple",
                "_spdisplays_vram": "1,536 MB",
                "spdisplays_metal": "Metal 3",
            }
        ]
    )

    def _fake_run(
        cmd: list[str], *args: Any, **kwargs: Any
    ) -> subprocess.CompletedProcess[bytes]:
        del args, kwargs
        return subprocess.CompletedProcess(cmd, 0, payload, b"")

    monitor = AppleGPUMonitor()
    monitor._smi_path = Path("/usr/sbin/system_profiler")
    monitor._smi_path_initialized = True
    monkeypatch.setattr(apple_module.subprocess, "run", _fake_run)

    gpus = monitor.get_gpu_info()

    assert len(gpus) == 1
    assert gpus[0].memory_total == 1536.0


def test_get_quick_metrics_disabled_by_default(monkeypatch) -> None:
    monitor = AppleGPUMonitor(enable_live_metrics=False)
    monitor._cached_gpu_info = [_gpu_stub(0)]

    monkeypatch.setattr(
        apple_module.subprocess,
        "run",
        lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("not expected")),
    )

    assert monitor.get_quick_metrics() == {}


def test_get_quick_metrics_enabled_parses_powermetrics(monkeypatch) -> None:
    monitor = AppleGPUMonitor(enable_live_metrics=True)
    monitor._cached_gpu_info = [_gpu_stub(0), _gpu_stub(1)]
    monitor._powermetrics_path = Path("/usr/bin/powermetrics")
    monitor._enable_live_metrics = True

    def _fake_run(
        cmd: list[str], *args: Any, **kwargs: Any
    ) -> subprocess.CompletedProcess[str]:
        del args, kwargs
        return subprocess.CompletedProcess(
            cmd,
            returncode=0,
            stdout="GPU HW active residency: 35.5%\n",
            stderr="",
        )

    monkeypatch.setattr(apple_module.subprocess, "run", _fake_run)

    assert monitor.get_quick_metrics() == {
        0: {"utilization": 35.5},
        1: {"utilization": 35.5},
    }
