"""Unit tests for remote_estop_node."""

import json
import time
import threading
from unittest.mock import MagicMock, patch

import pytest


def make_node(auto_estop=True, timeout=0.2):
    with patch("saltybot_bridge.remote_estop_node.serial.Serial"), \
         patch("saltybot_bridge.remote_estop_node.mqtt.Client"), \
         patch("saltybot_bridge.remote_estop_node.rclpy"), \
         patch("saltybot_bridge.remote_estop_node.Node.__init__", return_value=None):

        from saltybot_bridge.remote_estop_node import RemoteEstopNode
        node = RemoteEstopNode.__new__(RemoteEstopNode)
        node._ser = MagicMock()
        node._ser.is_open = True
        node._mqtt = MagicMock()
        node._mqtt_host = "localhost"
        node._mqtt_port = 1883
        node._mqtt_user = "test"
        node._mqtt_password = ""
        node._mqtt_topic = "test/estop"
        node._mqtt_keepalive = 10
        node._cellular_timeout_s = timeout
        node._auto_estop_on_disconnect = auto_estop
        node._auto_mode = False
        node._estop_active = False
        node._last_mqtt_ok = time.monotonic()
        node._watchdog_fired = False
        node._lock = threading.Lock()
        node._estop_pub = MagicMock()
        node.get_logger = MagicMock(return_value=MagicMock(
            info=MagicMock(), warning=MagicMock(), error=MagicMock()))
        return node


def mqtt_msg(payload_dict):
    msg = MagicMock()
    msg.payload = json.dumps(payload_dict).encode()
    return msg


def test_kill_true_sends_E():
    node = make_node()
    t0 = time.monotonic()
    node._on_mqtt_message(None, None, mqtt_msg({"kill": True}))
    assert (time.monotonic() - t0) < 0.5
    node._ser.write.assert_called_once_with(b'E\n')


def test_kill_false_sends_Z():
    node = make_node()
    node._on_mqtt_message(None, None, mqtt_msg({"kill": False}))
    node._ser.write.assert_called_once_with(b'Z\n')


def test_kill_true_publishes_active_true():
    node = make_node()
    node._on_mqtt_message(None, None, mqtt_msg({"kill": True}))
    assert node._estop_pub.publish.call_args[0][0].data is True


def test_bad_json_no_write():
    node = make_node()
    bad = MagicMock()
    bad.payload = b"NOT JSON"
    node._on_mqtt_message(None, None, bad)
    node._ser.write.assert_not_called()


def test_watchdog_fires_F_in_auto_after_timeout():
    node = make_node(auto_estop=True, timeout=0.05)
    node._auto_mode = True
    node._last_mqtt_ok = time.monotonic() - 1.0
    node._watchdog_cb()
    node._ser.write.assert_called_once_with(b'F\n')


def test_watchdog_no_fire_if_not_auto():
    node = make_node(auto_estop=True, timeout=0.05)
    node._auto_mode = False
    node._last_mqtt_ok = time.monotonic() - 1.0
    node._watchdog_cb()
    node._ser.write.assert_not_called()


def test_watchdog_only_fires_once():
    node = make_node(auto_estop=True, timeout=0.05)
    node._auto_mode = True
    node._last_mqtt_ok = time.monotonic() - 1.0
    node._watchdog_cb()
    node._watchdog_cb()
    assert node._ser.write.call_count == 1


def test_watchdog_disabled_by_param():
    node = make_node(auto_estop=False, timeout=0.05)
    node._auto_mode = True
    node._last_mqtt_ok = time.monotonic() - 1.0
    node._watchdog_cb()
    node._ser.write.assert_not_called()
