"""Tests for FrameDispatcher."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

from app.pipeline.frame_dispatcher import FrameDispatcher


def _make_frame():
    frame = MagicMock()
    frame.isValid.return_value = True
    return frame


class TestFrameDispatcher:
    def setup_method(self):
        # FrameDispatcher is a QObject — needs QApplication
        # Use minimal mock to avoid Qt dependency in unit tests
        with patch("app.pipeline.frame_dispatcher.QObject.__init__", return_value=None):
            self.dispatcher = FrameDispatcher.__new__(FrameDispatcher)
            self.dispatcher._subscribers = []
            self.dispatcher._frame_count = 0
            self.dispatcher._last_dispatch_time = 0.0

    def test_subscribe_adds_subscriber(self):
        cb = MagicMock()
        self.dispatcher.subscribe(cb)
        assert self.dispatcher.subscriber_count() == 1

    def test_subscribe_same_callback_twice_is_noop(self):
        cb = MagicMock()
        self.dispatcher.subscribe(cb)
        self.dispatcher.subscribe(cb)
        assert self.dispatcher.subscriber_count() == 1

    def test_unsubscribe_removes_subscriber(self):
        cb = MagicMock()
        self.dispatcher.subscribe(cb)
        self.dispatcher.unsubscribe(cb)
        assert self.dispatcher.subscriber_count() == 0

    def test_unsubscribe_nonexistent_does_not_raise(self):
        cb = MagicMock()
        self.dispatcher.unsubscribe(cb)  # should not raise

    def test_dispatch_calls_all_subscribers(self):
        cb1 = MagicMock()
        cb2 = MagicMock()
        self.dispatcher.subscribe(cb1)
        self.dispatcher.subscribe(cb2)
        frame = _make_frame()
        self.dispatcher.dispatch(frame)
        cb1.assert_called_once_with(frame)
        cb2.assert_called_once_with(frame)

    def test_dispatch_increments_frame_count(self):
        frame = _make_frame()
        self.dispatcher.dispatch(frame)
        self.dispatcher.dispatch(frame)
        assert self.dispatcher.frame_count == 2

    def test_dispatch_with_no_subscribers_does_not_raise(self):
        frame = _make_frame()
        self.dispatcher.dispatch(frame)  # should not raise

    def test_subscriber_exception_does_not_stop_others(self):
        def bad_cb(f):
            raise RuntimeError("boom")

        good_cb = MagicMock()
        self.dispatcher.subscribe(bad_cb)
        self.dispatcher.subscribe(good_cb)
        frame = _make_frame()
        self.dispatcher.dispatch(frame)  # should not raise
        good_cb.assert_called_once_with(frame)
