"""Pins for ``mgf.hrb.fmt`` — the medium-agnostic formatting helpers.

These tests are the canonical contract for the four fmt helpers (PR-06
reference implementation). Any UI consumer relying on a specific
output shape will land on these pins; any future shape change MUST
update the tests in lock-step so consumer-visible drift is loud
rather than silent.

Coverage breakdown:
  * boundary values per unit cascade (1 unit below / at / above each
    transition);
  * defensive paths (negative, non-int, None) — should return the
    "zero-shaped" output, never raise;
  * canonical examples from the docstrings — keeps the docstrings
    honest (broken docstring example → broken test).
"""

from __future__ import annotations

from mgf.hrb.diagnostics import Severity
from mgf.hrb.fmt import (
    format_duration,
    format_rss,
    format_severity_glyph,
    format_uptime,
)

# ---------------------------------------------------------------------------
# format_severity_glyph
# ---------------------------------------------------------------------------


def test_severity_glyph_each_known_value() -> None:
    """Every Severity enum member maps to a non-empty 5-char glyph."""
    for sev in Severity:
        glyph = format_severity_glyph(sev)
        assert len(glyph) == 5, f"Severity.{sev.name} → {glyph!r} (expected 5 chars)"


def test_severity_glyph_canonical_text() -> None:
    """The 5-char shape is the same one the CLI's `_doctor.py` used
    pre-extraction. Pinning here guards against accidental width drift
    that would mis-align operator-facing reports."""
    assert format_severity_glyph(Severity.OK) == "  OK "
    assert format_severity_glyph(Severity.WARN) == " WARN"
    assert format_severity_glyph(Severity.FAIL) == " FAIL"
    assert format_severity_glyph(Severity.DEFERRED) == " ... "


def test_severity_glyph_accepts_raw_string() -> None:
    """A consumer that received a wire-shape ``"ok"`` string doesn't
    need to enum-wrap before formatting."""
    assert format_severity_glyph("ok") == "  OK "
    assert format_severity_glyph("warn") == " WARN"


def test_severity_glyph_unknown_value_renders_question() -> None:
    """An unrecognised severity (broker introduced a new value)
    renders as a 5-char question-mark placeholder — honest "I don't
    know" rather than picking a wrong glyph."""
    assert format_severity_glyph("unknown_new_severity") == "  ?  "
    assert len(format_severity_glyph("anything")) == 5


# ---------------------------------------------------------------------------
# format_uptime
# ---------------------------------------------------------------------------


def test_uptime_sub_second_is_zero() -> None:
    """Sub-second uptimes truncate to '0s' — the daemon is essentially
    just-started; sub-second precision adds noise without information."""
    assert format_uptime(0) == "0s"
    assert format_uptime(500) == "0s"
    assert format_uptime(999) == "0s"


def test_uptime_seconds_only_under_a_minute() -> None:
    """1..59 seconds renders as bare 'Ns' (no leading 0m)."""
    assert format_uptime(1_000) == "1s"
    assert format_uptime(30_000) == "30s"
    assert format_uptime(59_000) == "59s"


def test_uptime_includes_minutes_at_60s_boundary() -> None:
    """60s = '1m 0s'; the minute component appears once it's non-zero."""
    assert format_uptime(60_000) == "1m 0s"
    assert format_uptime(125_000) == "2m 5s"
    assert format_uptime(3_599_000) == "59m 59s"


def test_uptime_includes_hours_at_1h_boundary() -> None:
    """3600s = '1h 0m 0s'; hours appear once non-zero."""
    assert format_uptime(3_600_000) == "1h 0m 0s"
    assert format_uptime(3_725_000) == "1h 2m 5s"


def test_uptime_long_daemon() -> None:
    """A 24h daemon renders correctly; no hour-cap."""
    assert format_uptime(86_400_000) == "24h 0m 0s"


def test_uptime_defensive_negative_returns_zero() -> None:
    """A broker bug emitting negative uptime mustn't crash the SDK —
    coerce to zero and render normally."""
    assert format_uptime(-1) == "0s"
    assert format_uptime(-999_999) == "0s"


def test_uptime_rejects_non_int_type() -> None:
    """Post-S1 (2026-05-26): non-int types raise TypeError instead
    of being silently coerced. A broker emitting the wrong wire
    type is a translation defect; fail loud rather than render
    nonsense. bool is rejected explicitly because bool IS int
    (Python treats it that way) and 'True hours of uptime' is
    always a programmer bug."""
    import pytest as _pytest

    with _pytest.raises(TypeError, match="expects int, got str"):
        format_uptime("not a number")  # type: ignore[arg-type]
    with _pytest.raises(TypeError, match="expects int, got NoneType"):
        format_uptime(None)  # type: ignore[arg-type]
    with _pytest.raises(TypeError, match="expects int, got bool"):
        format_uptime(True)  # type: ignore[arg-type]
    with _pytest.raises(TypeError, match="expects int, got float"):
        format_uptime(1.5)  # type: ignore[arg-type]


# ---------------------------------------------------------------------------
# format_duration
# ---------------------------------------------------------------------------


def test_duration_microseconds_band() -> None:
    """0..999 µs renders as bare 'Nµs'."""
    assert format_duration(0) == "0µs"
    assert format_duration(1) == "1µs"
    assert format_duration(500) == "500µs"
    assert format_duration(999) == "999µs"


def test_duration_milliseconds_band() -> None:
    """1ms..999ms renders as 'X.Yms' (one decimal)."""
    assert format_duration(1_000) == "1.0ms"
    assert format_duration(4_500) == "4.5ms"
    assert format_duration(999_000) == "999.0ms"


def test_duration_seconds_band() -> None:
    """1s..59s renders as 'X.Ys'."""
    assert format_duration(1_000_000) == "1.0s"
    assert format_duration(1_200_000) == "1.2s"
    assert format_duration(59_500_000) == "59.5s"


def test_duration_minutes_band_no_decimals() -> None:
    """≥1 minute drops to whole-second precision; the operator
    doesn't care about milliseconds inside a multi-minute audit
    record."""
    assert format_duration(60_000_000) == "1m0s"
    assert format_duration(312_000_000) == "5m12s"


def test_duration_defensive_negative_returns_zero() -> None:
    assert format_duration(-1) == "0µs"
    assert format_duration(-1_000_000) == "0µs"


def test_duration_rejects_non_int_type() -> None:
    """Post-S1: non-int types raise TypeError. See test_uptime
    rationale; identical contract."""
    import pytest as _pytest

    with _pytest.raises(TypeError, match="expects int, got str"):
        format_duration("not a number")  # type: ignore[arg-type]
    with _pytest.raises(TypeError, match="expects int, got NoneType"):
        format_duration(None)  # type: ignore[arg-type]
    with _pytest.raises(TypeError, match="expects int, got bool"):
        format_duration(False)  # type: ignore[arg-type]
    with _pytest.raises(TypeError, match="expects int, got float"):
        format_duration(2.5)  # type: ignore[arg-type]


# ---------------------------------------------------------------------------
# format_rss
# ---------------------------------------------------------------------------


def test_rss_bytes_band() -> None:
    """0..1023 bytes renders as bare 'N B'."""
    assert format_rss(0) == "0 B"
    assert format_rss(1) == "1 B"
    assert format_rss(1023) == "1023 B"


def test_rss_kib_band() -> None:
    """1024 B = 1.0 KiB; 1.5 KiB and friends."""
    assert format_rss(1024) == "1.0 KiB"
    assert format_rss(1_500) == "1.5 KiB"


def test_rss_mib_band() -> None:
    """Real-world broker RSS lands here. ~42 MiB matches the
    docstring example."""
    assert format_rss(44_400_000).endswith("MiB")
    # Exact: 44_400_000 / (1024*1024) ≈ 42.34
    assert format_rss(44_400_000) == "42.3 MiB"


def test_rss_gib_band() -> None:
    """A GUI process holding many open windows could plausibly
    reach GiB; verify the unit transitions cleanly."""
    assert format_rss(1_500_000_000).endswith("GiB")


def test_rss_defensive_negative_returns_zero() -> None:
    assert format_rss(-1) == "0 B"
    assert format_rss(-999_999) == "0 B"


def test_rss_rejects_non_int_type() -> None:
    """Post-S1: non-int types raise TypeError. See test_uptime
    rationale; identical contract."""
    import pytest as _pytest

    with _pytest.raises(TypeError, match="expects int, got str"):
        format_rss("not a number")  # type: ignore[arg-type]
    with _pytest.raises(TypeError, match="expects int, got NoneType"):
        format_rss(None)  # type: ignore[arg-type]
    with _pytest.raises(TypeError, match="expects int, got bool"):
        format_rss(True)  # type: ignore[arg-type]
    with _pytest.raises(TypeError, match="expects int, got float"):
        format_rss(1024.0)  # type: ignore[arg-type]


def test_rss_uses_binary_prefixes_not_decimal() -> None:
    """The library deliberately uses 1024-based (binary) prefixes
    to match Linux memory-reporting conventions. 1_000_000 bytes
    should render as ~977 KiB (binary), NOT 1.0 MB (decimal). This
    test guards against accidentally swapping to SI units later."""
    assert format_rss(1_000_000) == "976.6 KiB"
    # NOT "1.0 MB" — that would be a regression.
    assert "MB" not in format_rss(1_000_000)
