"""Tests for src/pygui/backend/visualization/graph_quick_item.py."""
import pytest

pytestmark = pytest.mark.usefixtures("qapp")


def test_no_data_initially():
    from pygui.backend.visualization.graph_quick_item import GraphQuickItem

    item = GraphQuickItem()
    assert item.seriesData == []
    assert item.sensorNames == []


def test_series_data_setter_stores_series():
    from pygui.backend.visualization.graph_quick_item import GraphQuickItem

    item = GraphQuickItem()
    item.seriesData = [[150.0, 151.0], [148.0, 147.5]]

    assert item.seriesData == [[150.0, 151.0], [148.0, 147.5]]


def test_series_data_auto_ranges_y_with_padding():
    from pygui.backend.visualization.graph_quick_item import GraphQuickItem

    item = GraphQuickItem()
    item.seriesData = [[150.0, 100.0], [200.0, 120.0]]

    # min=100, max=200, range=100, 10% pad => [90, 210]
    assert item._min_y == pytest.approx(90.0)
    assert item._max_y == pytest.approx(210.0)


def test_series_data_empty_keeps_default_range():
    from pygui.backend.visualization.graph_quick_item import GraphQuickItem

    item = GraphQuickItem()
    item.seriesData = []

    assert item._min_y == 0.0
    assert item._max_y == 150.0


def test_sensor_names_setter_stores_names():
    from pygui.backend.visualization.graph_quick_item import GraphQuickItem

    item = GraphQuickItem()
    item.sensorNames = ["Sensor 1", "Sensor 2"]

    assert item.sensorNames == ["Sensor 1", "Sensor 2"]


def test_non_numeric_values_are_skipped_not_crashing():
    from pygui.backend.visualization.graph_quick_item import GraphQuickItem

    item = GraphQuickItem()
    item.seriesData = [[150.0, "bad", 151.0]]

    assert item._min_y == pytest.approx(149.9)
    assert item._max_y == pytest.approx(151.1)


# ---- viewport_stats (replay chart min/max/avg readouts) ----
# Independently hand-computed against PopupChartForm.cs's recalc_stats
# semantics: aggregate across every sensor's points in [start, end], plus
# which sensor achieved each extreme.


def test_viewport_stats_full_range_aggregate():
    from pygui.backend.visualization.graph_quick_item import viewport_stats

    series = [[150.0, 151.0, 152.0], [148.0, 147.5, 147.0]]
    names = ["Sensor 1", "Sensor 2"]

    vmin, vmax, avg, min_sensor, max_sensor = viewport_stats(series, names, 0, 2)

    assert vmin == pytest.approx(147.0)
    assert vmax == pytest.approx(152.0)
    assert avg == pytest.approx(149.25)
    assert min_sensor == "Sensor 2"
    assert max_sensor == "Sensor 1"


def test_viewport_stats_restricted_to_subrange():
    from pygui.backend.visualization.graph_quick_item import viewport_stats

    series = [[150.0, 151.0, 152.0], [148.0, 147.5, 147.0]]
    names = ["Sensor 1", "Sensor 2"]

    vmin, vmax, avg, min_sensor, max_sensor = viewport_stats(series, names, 0, 1)

    assert vmin == pytest.approx(147.5)
    assert vmax == pytest.approx(151.0)
    assert avg == pytest.approx(149.125)
    assert min_sensor == "Sensor 2"
    assert max_sensor == "Sensor 1"


def test_viewport_stats_empty_series_returns_zeros():
    from pygui.backend.visualization.graph_quick_item import viewport_stats

    assert viewport_stats([], [], 0, -1) == (0.0, 0.0, 0.0, "", "")


def test_viewport_stats_skips_non_numeric():
    from pygui.backend.visualization.graph_quick_item import viewport_stats

    series = [[150.0, "bad", 151.0]]
    names = ["Sensor 1"]

    vmin, vmax, avg, min_sensor, max_sensor = viewport_stats(series, names, 0, 2)

    assert vmin == pytest.approx(150.0)
    assert vmax == pytest.approx(151.0)
    assert avg == pytest.approx(150.5)
    assert min_sensor == "Sensor 1"
    assert max_sensor == "Sensor 1"


# ---- GraphQuickItem viewport properties (replay chart) ----
# Default viewport (0, -1) means "whole series" so the already-shipped Graph
# tab (which never sets these) is unaffected — see docs/adr/0004.


def test_viewport_defaults_to_whole_range_and_markers_off():
    from pygui.backend.visualization.graph_quick_item import GraphQuickItem

    item = GraphQuickItem()

    assert item.viewStartIndex == 0
    assert item.viewEndIndex == -1
    assert item.showMinMaxMarkers is False


def test_view_stats_reflect_default_full_range_viewport():
    from pygui.backend.visualization.graph_quick_item import GraphQuickItem

    item = GraphQuickItem()
    item.sensorNames = ["Sensor 1", "Sensor 2"]
    item.seriesData = [[150.0, 151.0, 152.0], [148.0, 147.5, 147.0]]

    assert item.viewMin == pytest.approx(147.0)
    assert item.viewMax == pytest.approx(152.0)
    assert item.viewAvg == pytest.approx(149.25)
    assert item.viewMinSensor == "Sensor 2"
    assert item.viewMaxSensor == "Sensor 1"


def test_view_stats_restrict_to_narrowed_viewport():
    from pygui.backend.visualization.graph_quick_item import GraphQuickItem

    item = GraphQuickItem()
    item.sensorNames = ["Sensor 1", "Sensor 2"]
    item.seriesData = [[150.0, 151.0, 152.0], [148.0, 147.5, 147.0]]

    item.viewStartIndex = 0
    item.viewEndIndex = 1

    assert item.viewMin == pytest.approx(147.5)
    assert item.viewMax == pytest.approx(151.0)
    assert item.viewAvg == pytest.approx(149.125)


def test_auto_range_y_restricted_to_narrowed_viewport():
    from pygui.backend.visualization.graph_quick_item import GraphQuickItem

    item = GraphQuickItem()
    item.seriesData = [[150.0, 100.0], [200.0, 120.0]]
    # Full-range Y (pre-existing behavior): min=100, max=200 -> [90, 210]
    assert item._min_y == pytest.approx(90.0)
    assert item._max_y == pytest.approx(210.0)

    # Narrow the viewport to index 0 only: min=150, max=200, range=50, 10% pad
    item.viewStartIndex = 0
    item.viewEndIndex = 0

    assert item._min_y == pytest.approx(145.0)
    assert item._max_y == pytest.approx(205.0)


# ---- zoomAtFraction / panByFraction / resetZoom (replay chart interaction) ----
# 11-point series (indices 0..10) chosen so the zoom/pan arithmetic lands on
# whole numbers -- avoids rounding ambiguity in the independent hand trace.


def _eleven_point_item():
    from pygui.backend.visualization.graph_quick_item import GraphQuickItem

    item = GraphQuickItem()
    item.seriesData = [[float(i) for i in range(11)]]
    return item


def test_zoom_in_centers_on_fraction():
    item = _eleven_point_item()

    # Full range is (0, 10), width 10. Zooming to 40% width centered at the
    # midpoint (frac=0.5): center=5, new_width=4 -> new_start=3, new_end=7.
    item.zoomAtFraction(0.5, 0.4)

    assert item.viewStartIndex == 3
    assert item.viewEndIndex == 7


def test_zoom_out_clamps_to_data_bounds():
    item = _eleven_point_item()
    item.viewStartIndex = 3
    item.viewEndIndex = 7

    # width=4, zoom out 3x centered at midpoint (frac=0.5): requested
    # new_width=12 exceeds the data's max width of 10, so it clamps to the
    # full range (0, 10).
    item.zoomAtFraction(0.5, 3.0)

    assert item.viewStartIndex == 0
    assert item.viewEndIndex == 10


def test_pan_shifts_viewport_by_fraction_of_width():
    item = _eleven_point_item()
    item.viewStartIndex = 4
    item.viewEndIndex = 8

    # width=4, pan by 50% of width (2 indices): (4,8) -> (6,10)
    item.panByFraction(0.5)

    assert item.viewStartIndex == 6
    assert item.viewEndIndex == 10


def test_pan_clamps_at_right_edge_preserving_width():
    item = _eleven_point_item()
    item.viewStartIndex = 4
    item.viewEndIndex = 8

    # width=4, pan by 100% of width (4 indices) would be (8,12); index 12 is
    # out of bounds (max index 10), so the whole window shifts back by 2 to
    # stay in bounds while keeping width=4: (6,10).
    item.panByFraction(1.0)

    assert item.viewStartIndex == 6
    assert item.viewEndIndex == 10


def test_reset_zoom_restores_full_range_sentinel():
    item = _eleven_point_item()
    item.viewStartIndex = 3
    item.viewEndIndex = 7

    item.resetZoom()

    assert item.viewStartIndex == 0
    assert item.viewEndIndex == -1


def test_zoom_noop_when_fewer_than_two_points():
    from pygui.backend.visualization.graph_quick_item import GraphQuickItem

    item = GraphQuickItem()
    item.seriesData = [[42.0]]

    item.zoomAtFraction(0.5, 0.5)

    assert item.viewStartIndex == 0
    assert item.viewEndIndex == -1


def test_loading_new_series_while_zoomed_resets_viewport_to_full_range():
    """Regression: zooming near the tail of a long run, then loading a new
    (shorter) file, left the stale viewport pointing past the new data --
    every per-series slice became empty and the chart went blank with a
    stale Y-range and zeroed-out min/max/avg readouts. New data must reset
    the viewport to the full range (PopupChartForm.cs's SetData() parity).
    """
    from pygui.backend.visualization.graph_quick_item import GraphQuickItem

    item = GraphQuickItem()
    item.seriesData = [[float(i) for i in range(20)]]
    item.viewStartIndex = 15
    item.viewEndIndex = 19

    item.seriesData = [[100.0, 101.0, 102.0, 103.0, 104.0]]

    assert item.viewStartIndex == 0
    assert item.viewEndIndex == -1
    assert item.viewMin == pytest.approx(100.0)
    assert item.viewMax == pytest.approx(104.0)
    assert item.viewAvg == pytest.approx(102.0)
