"""
Unit tests for EchoDaemon helper methods (no IOC).

Run fast suite only::

    pytest -m "not integration"
    pytest -m unit
"""

from __future__ import annotations

import logging
import sys
from pathlib import Path

import numpy as np
import pytest

from epics_bridge import BridgeDaemon, LoggingManager, TaskStatus

_EXAMPLES = Path(__file__).resolve().parent.parent / "examples" / "echo_daemon"
if str(_EXAMPLES) not in sys.path:
    sys.path.insert(0, str(_EXAMPLES))

from echo_daemon import EchoDaemon  # noqa: E402
from echo_interface import EchoInterface  # noqa: E402

pytestmark = pytest.mark.unit


class EchoDaemonNoImgInit(EchoDaemon):
    """EchoDaemon without set_pva_image() for unit tests."""

    def set_pva_image(self) -> None:
        pass


@pytest.fixture
def echo_iface() -> EchoInterface:
    return EchoInterface(prefixes={"main": "T:", "echo": "E:", "pva": "P:"})


@pytest.fixture
def echo_daemon(mocker, echo_iface: EchoInterface) -> EchoDaemonNoImgInit:
    mocker.patch.object(BridgeDaemon, "_initialize_pv_state", lambda self: None)
    logging_manager = mocker.create_autospec(LoggingManager, instance=True)
    return EchoDaemonNoImgInit(echo_iface, logging_manager)


def test_skip_if_requested_returns_skipped_when_flag_set(
    mocker, echo_daemon: EchoDaemonNoImgInit
) -> None:
    echo_daemon.iface.skip_cycle.val = True
    pvget = mocker.patch.object(echo_daemon.io, "pvget")

    assert echo_daemon._skip_if_requested() is TaskStatus.SKIPPED
    pvget.assert_called_once_with(echo_daemon.iface.skip_cycle)


def test_skip_if_requested_returns_none_when_not_skipped(
    mocker, echo_daemon: EchoDaemonNoImgInit
) -> None:
    echo_daemon.iface.skip_cycle.val = False
    mocker.patch.object(echo_daemon.io, "pvget")

    assert echo_daemon._skip_if_requested() is None


def test_fetch_inputs_builds_list_and_pvgets(
    mocker, echo_daemon: EchoDaemonNoImgInit
) -> None:
    pvs = echo_daemon._input_pvs()
    pvget = mocker.patch.object(echo_daemon.io, "pvget")

    echo_daemon._fetch_inputs()

    pvget.assert_called_once_with(pvs)


def test_read_echo_inputs_applies_defaults_and_enum_wrap(
    echo_daemon: EchoDaemonNoImgInit,
) -> None:
    iface = echo_daemon.iface
    iface.inp_float.val = None
    iface.inp_int.val = None
    iface.inp_str.val = None
    iface.inp_enum.val = 2
    iface.inp_arr.val = None
    iface.img.val = None

    r = echo_daemon._read_echo_inputs()

    assert r.f_val == 0.0
    assert r.i_val == 0
    assert r.s_val == ""
    assert r.e_val == 2
    assert r.out_enum == 0
    assert r.arr_val == []
    assert r.img_val is None


def test_read_echo_inputs_preserves_scalar_and_array_values(
    echo_daemon: EchoDaemonNoImgInit,
) -> None:
    iface = echo_daemon.iface
    arr = np.array([1, 2, 3], dtype=np.int64)
    img = np.array([10], dtype=np.int64)
    iface.inp_float.val = 4.0
    iface.inp_int.val = 5
    iface.inp_str.val = "x"
    iface.inp_enum.val = 0
    iface.inp_arr.val = arr
    iface.img.val = img

    r = echo_daemon._read_echo_inputs()

    assert r.f_val == 4.0
    assert r.i_val == 5
    assert r.s_val == "x"
    assert r.e_val == 0
    assert r.out_enum == 1
    np.testing.assert_array_equal(r.arr_val, arr)
    np.testing.assert_array_equal(r.img_val, img)


def test_log_echo_inputs_emits_debug(caplog, echo_daemon: EchoDaemonNoImgInit) -> None:
    r = echo_daemon._EchoReadings(
        f_val=1.0,
        i_val=2,
        s_val="hi",
        e_val=0,
        out_enum=1,
        arr_val=[1, 2],
        img_val=None,
    )
    caplog.set_level(logging.DEBUG)

    echo_daemon._log_echo_inputs(r)

    assert any("inputs read" in rec.message for rec in caplog.records)


def test_write_echo_outputs_applies_transforms(
    echo_daemon: EchoDaemonNoImgInit,
) -> None:
    iface = echo_daemon.iface
    arr = np.array([1, 2])
    img = np.array([3])
    r = echo_daemon._EchoReadings(
        f_val=2.0,
        i_val=10,
        s_val="ab",
        e_val=0,
        out_enum=2,
        arr_val=arr,
        img_val=img,
    )

    echo_daemon._write_echo_outputs(r)

    assert iface.out_float.val == 5.0
    assert iface.out_int.val == 110
    assert iface.out_str.val == "Echo: ab"
    assert iface.out_enum.val == 2
    np.testing.assert_array_equal(iface.out_arr.val, arr * 2)
    np.testing.assert_array_equal(iface.img.val, img * 10)


def test_write_echo_outputs_clears_img_when_none(
    echo_daemon: EchoDaemonNoImgInit,
) -> None:
    r = echo_daemon._EchoReadings(
        f_val=0.0,
        i_val=0,
        s_val="",
        e_val=0,
        out_enum=0,
        arr_val=[],
        img_val=None,
    )

    echo_daemon._write_echo_outputs(r)

    assert echo_daemon.iface.img.val is None


def test_put_outputs_success(mocker, echo_daemon: EchoDaemonNoImgInit) -> None:
    pvput = mocker.patch.object(echo_daemon.io, "pvput")

    assert echo_daemon._put_outputs() is None

    pvs = echo_daemon._output_pvs()
    pvput.assert_called_once_with(pvs)


def test_put_outputs_bubbles_put_error(
    mocker, echo_daemon: EchoDaemonNoImgInit
) -> None:
    mocker.patch.object(
        echo_daemon.io, "pvput", side_effect=TimeoutError("unavailable")
    )

    with pytest.raises(TimeoutError, match="unavailable"):
        echo_daemon._put_outputs()


def test_echo_interface_filter_pvs_groups_inputs_outputs_image(
    echo_iface: EchoInterface,
) -> None:
    input_pvs = set(echo_iface.filter_pvs("input"))
    output_pvs = set(echo_iface.filter_pvs("output"))
    image_pvs = set(echo_iface.filter_pvs("image"))
    control_pvs = set(echo_iface.filter_pvs("control"))

    assert input_pvs == {
        echo_iface.inp_float,
        echo_iface.inp_int,
        echo_iface.inp_str,
        echo_iface.inp_enum,
        echo_iface.inp_arr,
    }
    assert output_pvs == {
        echo_iface.out_float,
        echo_iface.out_int,
        echo_iface.out_str,
        echo_iface.out_enum,
        echo_iface.out_arr,
    }
    assert image_pvs == {echo_iface.img, echo_iface.img_xsize, echo_iface.img_ysize}
    assert echo_iface.skip_cycle in control_pvs
    assert echo_iface.trigger in control_pvs
    assert echo_iface.heartbeat in control_pvs
