"""Hardware test for the Owon serial PSU.

Validates basic SCPI control via :class:`OwonPSU` against the
**session-managed** PSU (see :mod:`tests.hardware.conftest`):

  - identification (`*IDN?`)
  - decoded output state (`output?`)
  - parsed measurement queries (`MEAS:VOLT?`, `MEAS:CURR?`)

The session-scoped autouse fixture in ``conftest.py`` opens the PSU
once at session start, parks it at the configured nominal voltage,
enables output, and leaves it that way for the whole session. This
test therefore does **not** toggle the output — calling
``set_output(False)`` would brown out the ECU and break every MUM
test that runs afterwards.

The four-phase template (SETUP / PROCEDURE / ASSERT / TEARDOWN) still
applies, but TEARDOWN is empty: the test reads-only and leaves the
bench exactly as it found it.
"""
from __future__ import annotations

import pytest

from ecu_framework.config import EcuTestConfig
from ecu_framework.power import OwonPSU


pytestmark = [pytest.mark.hardware]


def test_owon_psu_idn_and_measurements(config: EcuTestConfig, psu: OwonPSU, rp):
    """
    Title: Owon PSU — IDN, output state, and parsed measurements

    Description:
        Read-only smoke test for the Owon PSU controller. Confirms the
        bench PSU responds to ``*IDN?``, reports an enabled output
        (the session fixture parked it there), and returns parseable
        floats for ``MEAS:VOLT?`` and ``MEAS:CURR?``. Optionally
        verifies the IDN matches the configured substring.

    Requirements: REQ-PSU-001

    Test Steps:
        1. SETUP:    none — the session fixture opened the port,
                     parked the PSU at nominal, and enabled output
                     before any test in this run started
        2. PROCEDURE: query *IDN?, output?, MEAS:VOLT?, MEAS:CURR?
        3. ASSERT:   IDN is non-empty (and contains ``idn_substr`` if
                     configured); output is reported ON; both
                     measurements parse to floats
        4. TEARDOWN: none — this test does not mutate bench state

    Expected Result:
        - IDN is non-empty (and contains ``idn_substr`` when set)
        - ``output_is_on()`` returns True (bench is powered)
        - ``measure_voltage_v()`` returns a float close to nominal
        - ``measure_current_a()`` returns a float ≥ 0
    """
    psu_cfg = config.power_supply
    want_substr = psu_cfg.idn_substr
    expected_v = float(psu_cfg.set_voltage) if psu_cfg.set_voltage else None

    # ── PROCEDURE ─────────────────────────────────────────────────────
    # All four queries are reads — they don't change the bench.
    idn = psu.idn()
    is_on = psu.output_is_on()
    measured_v = psu.measure_voltage_v()
    measured_i = psu.measure_current_a()

    print(f"PSU IDN: {idn}")
    print(f"Output ON: {is_on}")
    print(f"Measured: V={measured_v}V, I={measured_i}A "
          f"(nominal setpoint: {expected_v}V)")

    # ── ASSERT ────────────────────────────────────────────────────────
    # Record diagnostics before assertions so failure investigations
    # have the captured values.
    rp("psu_idn", idn)
    rp("output_is_on", bool(is_on))
    rp("measured_voltage_v", measured_v)
    rp("measured_current_a", measured_i)
    rp("expected_voltage_v", expected_v)

    assert isinstance(idn, str) and idn, "*IDN? returned empty response"
    if want_substr:
        assert str(want_substr).lower() in idn.lower(), (
            f"IDN does not contain expected substring: {want_substr!r}. "
            f"Got: {idn!r}"
        )

    # The session fixture parked the PSU with output enabled. If this
    # comes back False the bench is in an unexpected state — likely
    # something in a preceding test mistakenly turned the output off.
    assert is_on is True, (
        f"PSU output is not ON ({is_on=!r}). The session fixture parks "
        f"output=ON at start; some earlier test or the fixture itself "
        f"may have disabled it. Tests must NOT call psu.set_output(False)."
    )

    # Measurements must parse — surfaces firmware-level response
    # format mismatches as a clear failure.
    assert measured_v is not None, (
        "measure_voltage_v() returned no number; "
        "check the firmware's MEAS:VOLT? response format"
    )
    assert measured_i is not None, (
        "measure_current_a() returned no number; "
        "check the firmware's MEAS:CURR? response format"
    )

    # Sanity: measured voltage should be within ±10% of the nominal
    # setpoint when the bench is steady. Loose tolerance because PSU
    # accuracy + meter noise + cable drop all stack up.
    if expected_v is not None:
        tol = 0.10 * expected_v
        assert abs(measured_v - expected_v) <= tol, (
            f"Measured {measured_v}V is outside ±10% of nominal {expected_v}V "
            f"(tolerance ±{tol:.2f}V). Bench supply may be drifting or the "
            f"PSU isn't connected to its measure points."
        )
