"""Tx span UI smoke tests."""

import matplotlib

matplotlib.use("Agg")

from typing import cast

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backend_bases import Event

from envelope_decay_fit.segmentation.tx_span import (
    TxSpanUI,
    _clamp_span_to_domain,
    _compute_log_envelope_db,
    _snap_t0_to_y_target,
)


def test_tx_span_ui_launch_sanity(monkeypatch) -> None:
    """Ensure the Tx span UI can launch headlessly."""
    t = np.linspace(0.0, 1.0, 200)
    env = np.exp(-3.0 * t)

    ui = TxSpanUI(t=t, env=env, fn_hz=120.0)
    monkeypatch.setattr(plt, "show", lambda *args, **kwargs: None)

    measurement = ui.run()

    assert measurement is not None
    assert measurement.t0 == float(t[0])
    assert measurement.t1 == float(t[-1])
    assert measurement.Tx_value in ui.tx_options_db
    assert np.isfinite(measurement.slope_db_per_s)


def test_tx_span_ui_toggle_display_mode(monkeypatch) -> None:
    t = np.linspace(0.0, 1.0, 200)
    env = np.exp(-3.0 * t)

    ui = TxSpanUI(t=t, env=env, fn_hz=120.0)
    monkeypatch.setattr(plt, "show", lambda *args, **kwargs: None)
    ui.run()

    class EventStub:
        def __init__(self, key: str) -> None:
            self.key = key

    assert ui.display_mode == "db"
    ui._on_key(cast(Event, EventStub("l")))

    assert ui.display_mode == "amp"
    assert ui.env_line is not None
    np.testing.assert_allclose(np.asarray(ui.env_line.get_ydata()), np.abs(env))
    assert ui.ax.get_ylabel() == "Envelope amplitude"

    ui._on_key(cast(Event, EventStub("l")))
    assert ui.display_mode == "db"
    np.testing.assert_allclose(
        np.asarray(ui.env_line.get_ydata()), _compute_log_envelope_db(env)
    )
    assert ui.ax.get_ylabel() == "Envelope (dB re max)"


def test_tx_span_ui_overlay_amp_conversion(monkeypatch) -> None:
    t = np.linspace(0.0, 1.0, 200)
    env = np.exp(-2.0 * t)

    ui = TxSpanUI(t=t, env=env, fn_hz=120.0, tx_options_db=[20.0])
    monkeypatch.setattr(plt, "show", lambda *args, **kwargs: None)
    ui.run()

    class EventStub:
        def __init__(self, key: str) -> None:
            self.key = key

    ui._on_key(cast(Event, EventStub("l")))

    y0_amp = float(np.abs(env[0]))
    y1_amp = y0_amp * 10.0 ** (-20.0 / 20.0)
    assert ui.overlay_line is not None
    line_y = np.asarray(ui.overlay_line.get_ydata())
    np.testing.assert_allclose(line_y[0], y0_amp)
    np.testing.assert_allclose(line_y[-1], y1_amp)
    assert np.all(np.diff(line_y) <= 0.0)


def test_snap_t0_to_y_target_tie_breakers() -> None:
    t = np.asarray([0.0, 1.0, 2.0, 3.0])
    y = np.asarray([0.0, 1.0, 1.0, 2.0])

    t0 = _snap_t0_to_y_target(t, y, t_target=1.6, y_target=1.0, window_s=5.0)
    assert t0 == 2.0

    t0 = _snap_t0_to_y_target(t, y, t_target=1.4, y_target=1.0, window_s=5.0)
    assert t0 == 1.0

    t0 = _snap_t0_to_y_target(t, y, t_target=1.5, y_target=1.0, window_s=5.0)
    assert t0 == 1.0


def test_snap_t0_to_y_target_ignores_non_finite() -> None:
    t = np.asarray([0.0, 1.0, 2.0, 3.0])
    y = np.asarray([0.0, np.nan, 0.1, 0.2])

    t0 = _snap_t0_to_y_target(t, y, t_target=1.0, y_target=0.0, window_s=5.0)
    assert t0 == 0.0


def test_snap_t0_to_y_target_respects_window() -> None:
    t = np.linspace(0.0, 10.0, 11)
    y = t.copy()

    t0 = _snap_t0_to_y_target(t, y, t_target=5.0, y_target=100.0, window_s=1.0)
    assert t0 == 6.0


def test_clamp_span_to_domain() -> None:
    t = np.linspace(0.0, 10.0, 11)

    t0, t1 = _clamp_span_to_domain(8.0, 4.0, t)
    assert t0 == 6.0
    assert t1 == 10.0

    t0, t1 = _clamp_span_to_domain(-1.0, 4.0, t)
    assert t0 == 0.0
    assert t1 == 4.0
