"""Tests for src/pygui/backend/visualization/wafer_map_item.py."""
from unittest.mock import MagicMock

import pytest

# Use the session-scoped qapp fixture from conftest.py so Qt Property/Signal
# descriptors work properly.
pytestmark = pytest.mark.usefixtures("qapp")


def test_export_image_empty_path():
    """export_image returns False for empty/None path."""
    from pygui.backend.visualization.wafer_map_item import WaferMapItem

    item = WaferMapItem()
    assert item.export_image("") is False
    assert item.export_image(None) is False


def test_export_image_zero_size_item_fails():
    """export_image returns False when the item has no geometry to render."""
    from pygui.backend.visualization.wafer_map_item import WaferMapItem

    item = WaferMapItem()
    assert item.export_image("/tmp/test_wafer.png") is False


def test_export_image_unwritable_path_fails(tmp_path):
    """export_image returns False when the target directory doesn't exist."""
    from pygui.backend.visualization.wafer_map_item import WaferMapItem

    item = WaferMapItem()
    item.setWidth(100)
    item.setHeight(100)
    item.values = [10.0, 20.0, 30.0]
    assert item.export_image(str(tmp_path / "no_such_dir" / "wafer.png")) is False


def test_thickness_properties():
    """Thickness data properties exist with correct defaults."""
    from pygui.backend.visualization.wafer_map_item import WaferMapItem

    item = WaferMapItem()

    assert item._thickness_data == []
    assert item._show_thickness is False


def test_thickness_setter_updates_data():
    """Setting thicknessData stores [x, y, t2] triples."""
    from pygui.backend.visualization.wafer_map_item import WaferMapItem

    item = WaferMapItem()
    item._rebuild_thickness = MagicMock()

    item.thicknessData = [[0.0, 10.0, 1.5], [5.0, -5.0, 2.0]]

    assert item._thickness_data == [[0.0, 10.0, 1.5], [5.0, -5.0, 2.0]]
    assert item._rebuild_thickness.called
    assert item.hasThickness is True


THICKNESS_HEADER = "Lot Start Time,RC,Site#,Slot#,T2,NGOF,Wafer X,Wafer Y\n"


def _write_csv(tmp_path, body, header=THICKNESS_HEADER):
    p = tmp_path / "thickness.csv"
    p.write_text(header + body)
    return str(p)


def test_parse_thickness_csv_valid(tmp_path):
    from pygui.backend.visualization.wafer_map_item import parse_thickness_csv

    path = _write_csv(tmp_path,
                      "t,1,1,7,1.5,0,10.0,-20.0\n"
                      "t,1,2,7,2.5,0,-30.0,40.0\n")
    points, err = parse_thickness_csv(path)
    assert err == ""
    assert points == [[10.0, -20.0, 1.5], [-30.0, 40.0, 2.5]]


def test_parse_thickness_csv_first_slot_only(tmp_path):
    """Rows after the Slot# changes belong to another wafer and are skipped."""
    from pygui.backend.visualization.wafer_map_item import parse_thickness_csv

    path = _write_csv(tmp_path,
                      "t,1,1,7,1.5,0,10.0,-20.0\n"
                      "t,1,1,8,9.9,0,0.0,0.0\n")
    points, err = parse_thickness_csv(path)
    assert err == ""
    assert points == [[10.0, -20.0, 1.5]]


def test_parse_thickness_csv_bad_header(tmp_path):
    from pygui.backend.visualization.wafer_map_item import parse_thickness_csv

    path = _write_csv(tmp_path, "t,1,1,7,1.5,0,10.0,-20.0\n", header="A,B,C\n")
    points, err = parse_thickness_csv(path)
    assert points == []
    assert "header" in err


def test_parse_thickness_csv_malformed_value(tmp_path):
    from pygui.backend.visualization.wafer_map_item import parse_thickness_csv

    path = _write_csv(tmp_path, "t,1,1,7,oops,0,10.0,-20.0\n")
    points, err = parse_thickness_csv(path)
    assert points == []
    assert "Malformed" in err


def test_parse_thickness_csv_missing_file():
    from pygui.backend.visualization.wafer_map_item import parse_thickness_csv

    points, err = parse_thickness_csv("/no/such/file.csv")
    assert points == []
    assert "Unable to read" in err


def test_load_thickness_slot(tmp_path):
    """loadThickness feeds parsed points into thicknessData."""
    from pygui.backend.visualization.wafer_map_item import WaferMapItem

    item = WaferMapItem()
    path = _write_csv(tmp_path, "t,1,1,7,1.5,0,10.0,-20.0\n")
    assert item.loadThickness(path) == ""
    assert item.hasThickness is True

    assert item.loadThickness("/no/such/file.csv") != ""
    # Failed load keeps the previously loaded data.
    assert item.hasThickness is True


def test_show_thickness_setter():
    """Setting showThickness updates _show_thickness."""
    from pygui.backend.visualization.wafer_map_item import WaferMapItem

    item = WaferMapItem()
    assert item._show_thickness is False

    item.showThickness = True
    assert item._show_thickness is True


def test_export_image_writes_real_png_headless(tmp_path):
    """export_image must produce an actual decodable image file without a
    QQuickWindow — the button was silently failing because grabToImage()
    resolves asynchronously and never completes headless/immediately."""
    from PySide6.QtGui import QImage

    from pygui.backend.visualization.wafer_map_item import WaferMapItem

    item = WaferMapItem()
    item.setWidth(200)
    item.setHeight(200)
    item.values = [10.0, 20.0, 30.0]
    out = tmp_path / "wafer.png"

    assert item.export_image(str(out)) is True
    assert out.exists()
    loaded = QImage(str(out))
    assert not loaded.isNull()
    assert loaded.width() == 200


def test_export_image_includes_metrics_footer(tmp_path):
    """With sensor values loaded, the export gains a footer strip below the
    map carrying the metric readouts (sensors / min / max / avg)."""
    from PySide6.QtGui import QImage

    from pygui.backend.visualization.wafer_map_item import WaferMapItem

    item = WaferMapItem()
    item.setWidth(200)
    item.setHeight(200)
    item.values = [10.0, 20.0, 30.0]
    out = tmp_path / "wafer.png"

    assert item.export_image(str(out)) is True
    loaded = QImage(str(out))
    assert loaded.height() > 200  # footer strip appended
    # footer is painted opaque (dark bg), not transparent
    assert loaded.pixelColor(5, loaded.height() - 5).alpha() == 255


def test_export_image_extra_line_grows_footer(tmp_path):
    """Live export passes an extra metrics line; the footer gains a second
    row for it. Review export (no extra) keeps the single-row footer."""
    from PySide6.QtGui import QImage

    from pygui.backend.visualization.wafer_map_item import WaferMapItem

    item = WaferMapItem()
    item.setWidth(200)
    item.setHeight(200)
    item.values = [10.0, 20.0, 30.0]

    review = tmp_path / "review.png"
    live = tmp_path / "live.png"
    assert item.export_image(str(review)) is True
    assert item.export_image(str(live), "Frames: 42   Errors: 0") is True

    h_review = QImage(str(review)).height()
    h_live = QImage(str(live)).height()
    assert h_review > 200  # readout footer present
    assert h_live > h_review  # extra live row appended


def test_export_image_highlights_hottest_and_coldest(tmp_path):
    """The exported map rings the hottest sensor in the high color and the
    coldest in the low color. With no bands set every marker fill is the
    in-range green, so any high/low-colored pixels must come from the rings."""
    from PySide6.QtGui import QImage

    from pygui.backend.visualization.wafer_map_item import WaferMapItem

    item = WaferMapItem()
    item.setWidth(200)
    item.setHeight(200)
    item.sensors = [
        {"label": "1", "x": -50.0, "y": 0.0},
        {"label": "2", "x": 0.0, "y": 0.0},
        {"label": "3", "x": 50.0, "y": 0.0},
    ]
    item.values = [10.0, 20.0, 30.0]
    out = tmp_path / "wafer.png"
    assert item.export_image(str(out)) is True

    img = QImage(str(out))
    found = {img.pixelColor(x, y).name()
             for y in range(img.height()) for x in range(img.width())}
    assert "#ef4444" in found  # hottest ring (sensorHigh)
    assert "#5b9df5" in found  # coldest ring (sensorLow)


def test_show_extremes_toggle_gates_peak_rings(tmp_path):
    """showExtremes (Display setting) hides the hot/cold rings when off.
    Same pixel probe as the highlight test: with rings off, no high/low
    colored pixels should appear anywhere."""
    from PySide6.QtGui import QImage

    from pygui.backend.visualization.wafer_map_item import WaferMapItem

    item = WaferMapItem()
    assert item.showExtremes is True  # rings on by default

    item.setWidth(200)
    item.setHeight(200)
    item.sensors = [
        {"label": "1", "x": -50.0, "y": 0.0},
        {"label": "2", "x": 0.0, "y": 0.0},
        {"label": "3", "x": 50.0, "y": 0.0},
    ]
    item.values = [10.0, 20.0, 30.0]
    item.showExtremes = False
    out = tmp_path / "wafer.png"
    assert item.export_image(str(out)) is True

    img = QImage(str(out))
    found = {img.pixelColor(x, y).name()
             for y in range(img.height()) for x in range(img.width())}
    assert "#ef4444" not in found
    assert "#5b9df5" not in found


def test_readout_text_full_stats():
    """Export footer readout carries every READOUT stat with sensor indices.

    Worked example: [10, 20, 30] → min 10 (#1), max 30 (#3), diff 20,
    avg 20, σ = sqrt(200/3) ≈ 8.16, 3σ ≈ 24.49.
    """
    from pygui.backend.visualization.wafer_map_item import readout_text

    assert readout_text([10.0, 20.0, 30.0]) == (
        "Sensors: 3   Min: 10.00°C (#1)   Max: 30.00°C (#3)   "
        "Diff: 20.00   Avg: 20.00   σ: 8.16   3σ: 24.49"
    )


def test_readout_text_empty():
    from pygui.backend.visualization.wafer_map_item import readout_text

    assert readout_text([]) == ""


def test_export_image_no_data_fails(tmp_path):
    """No file loaded / no stream played -> nothing meaningful to export."""
    from pygui.backend.visualization.wafer_map_item import WaferMapItem

    item = WaferMapItem()
    item.setWidth(200)
    item.setHeight(200)
    out = tmp_path / "wafer.png"

    assert item.export_image(str(out)) is False
    assert not out.exists()
