"""Tests for hytop.gpu.parser."""

from __future__ import annotations

import json

import pytest

from hytop.gpu.parser import parse_hy_smi_output, parse_number, strip_ansi

# ---------------------------------------------------------------------------
# Real hy-smi JSON fixture (from actual 8-card Hygon DCU node)
# ---------------------------------------------------------------------------

# Full-flag output (--showtemp --showpower --showhcuclocks --showmemuse --showuse --json)
# Representative cards: card0 (idle) and card7 (100% HCU load)
HY_SMI_FULL = {
    "card0": {
        "Average Graphics Package Power (W)": "157.0",
        "Temperature (Sensor edge) (C)": "31.0",
        "Temperature (Sensor junction) (C)": "34.0",
        "Temperature (Sensor mem) (C)": "28.0",
        "Temperature (Sensor core) (C)": "30.0",
        "HCU use (%)": "0.0",
        "HCU memory use (%)": "89",
        "sclk clock level": "10",
        "sclk clock speed": "1500Mhz",
    },
    "card7": {
        "Average Graphics Package Power (W)": "141.0",
        "Temperature (Sensor edge) (C)": "28.0",
        "Temperature (Sensor junction) (C)": "33.0",
        "Temperature (Sensor mem) (C)": "25.0",
        "Temperature (Sensor core) (C)": "25.0",
        "HCU use (%)": "100.0",
        "HCU memory use (%)": "89",
        "sclk clock level": "10",
        "sclk clock speed": "1500Mhz",
    },
}

# Temp-only output (--showtemp --json): extra sensor keys should be ignored
HY_SMI_TEMP_ONLY = {
    "card0": {
        "Temperature (Sensor edge) (C)": "28.0",
        "Temperature (Sensor junction) (C)": "31.0",
        "Temperature (Sensor mem) (C)": "25.0",
        "Temperature (Sensor core) (C)": "26.0",
    },
}


# ---------------------------------------------------------------------------
# strip_ansi
# ---------------------------------------------------------------------------


class TestStripAnsi:
    def test_plain_text_unchanged(self):
        assert strip_ansi("hello") == "hello"

    def test_color_codes_removed(self):
        assert strip_ansi("\x1b[31mred\x1b[0m") == "red"

    def test_empty_string(self):
        assert strip_ansi("") == ""

    def test_multiple_codes(self):
        assert strip_ansi("\x1b[1m\x1b[4mbold\x1b[0m") == "bold"


# ---------------------------------------------------------------------------
# parse_number
# ---------------------------------------------------------------------------


class TestParseNumber:
    def test_integer_string(self):
        assert parse_number("89") == pytest.approx(89.0)

    def test_float_string(self):
        assert parse_number("157.0") == pytest.approx(157.0)

    def test_value_with_unit_suffix(self):
        # "1500Mhz" — real sclk clock speed format from hy-smi
        assert parse_number("1500Mhz") == pytest.approx(1500.0)

    def test_no_number_raises(self):
        with pytest.raises(ValueError, match="cannot parse"):
            parse_number("N/A")

    def test_negative_number(self):
        assert parse_number("-5.5") == pytest.approx(-5.5)


# ---------------------------------------------------------------------------
# parse_hy_smi_output — with real fixture data
# ---------------------------------------------------------------------------


class TestParseHySmiOutput:
    def test_full_output_card_count(self):
        raw = json.dumps(HY_SMI_FULL)
        result = parse_hy_smi_output(raw, sample_ts=1.0)
        assert set(result.keys()) == {0, 7}

    def test_full_output_card0_metrics(self):
        raw = json.dumps(HY_SMI_FULL)
        result = parse_hy_smi_output(raw, sample_ts=1.0)
        s = result[0]
        assert s.temp_c == pytest.approx(30.0)
        assert s.avg_pwr_w == pytest.approx(157.0)
        assert s.hcu_pct == pytest.approx(0.0)
        assert s.vram_pct == pytest.approx(89.0)  # integer string "89" → 89.0
        assert s.sclk_mhz == pytest.approx(1500.0)  # "1500Mhz" → 1500.0

    def test_full_output_card7_hcu_load(self):
        raw = json.dumps(HY_SMI_FULL)
        result = parse_hy_smi_output(raw, sample_ts=1.0)
        assert result[7].hcu_pct == pytest.approx(100.0)

    def test_temp_only_output(self):
        raw = json.dumps(HY_SMI_TEMP_ONLY)
        result = parse_hy_smi_output(raw, sample_ts=1.0)
        s = result[0]
        assert s.temp_c == pytest.approx(26.0)
        # Unrelated sensor keys must not populate fields
        assert s.avg_pwr_w is None
        assert s.hcu_pct is None

    def test_sample_ts_propagated(self):
        raw = json.dumps(HY_SMI_FULL)
        result = parse_hy_smi_output(raw, sample_ts=42.5)
        assert result[0].ts == pytest.approx(42.5)

    def test_unknown_card_keys_ignored(self):
        payload = {"sys_info": {"foo": "bar"}, "card0": HY_SMI_FULL["card0"]}
        result = parse_hy_smi_output(json.dumps(payload), sample_ts=1.0)
        assert list(result.keys()) == [0]

    def test_empty_string_returns_empty(self):
        assert parse_hy_smi_output("", sample_ts=1.0) == {}

    def test_invalid_json_returns_empty(self):
        assert parse_hy_smi_output("not json", sample_ts=1.0) == {}

    def test_ansi_stripped_before_parse(self):
        # Some hy-smi versions emit ANSI colors; parser must strip them first
        raw_with_ansi = "\x1b[0m" + json.dumps(HY_SMI_TEMP_ONLY) + "\x1b[0m"
        result = parse_hy_smi_output(raw_with_ansi, sample_ts=1.0)
        assert 0 in result
