import time

import pytest

from soniclue_basekit.daemon_thread import DaemonThread


# --- DaemonThread ---


class _CountingThread(DaemonThread):
    """DaemonThread subclass that counts iterations."""

    def _process(self) -> None:
        pass


class _RaisingThread(DaemonThread):
    """DaemonThread subclass whose _process always raises."""

    def _process(self) -> None:
        raise ValueError("boom")


def test_processed_count_starts_at_zero():
    t = _CountingThread()
    assert t.processed_count == 0


def test_processed_count_increments():
    t = _CountingThread(interval_sec=0)
    t.start()
    for _ in range(50):
        if t.processed_count >= 3:
            break
        time.sleep(0.01)
    t.set_terminating()
    t.join(timeout=2)
    assert t.processed_count >= 3


def test_thread_stops_after_set_terminating():
    t = _CountingThread(interval_sec=0)
    t.start()
    t.set_terminating()
    t.join(timeout=2)
    assert not t.is_alive()


def test_error_notifier_called_on_exception():
    errors = []
    t = _RaisingThread(interval_sec=0, error_notifier=lambda msg, ex: errors.append((msg, ex)))
    t.start()
    for _ in range(50):
        if errors:
            break
        time.sleep(0.01)
    t.set_terminating()
    t.join(timeout=2)
    assert errors
    msg, ex = errors[0]
    assert isinstance(ex, ValueError)
    assert "boom" in msg


def test_no_error_notifier_does_not_raise():
    t = _RaisingThread(interval_sec=0)
    t.start()
    time.sleep(0.05)
    t.set_terminating()
    t.join(timeout=2)
    assert not t.is_alive()


def test_pause_stops_processing():
    t = _CountingThread(interval_sec=0)
    t.start()
    # Let it run a bit, then pause.
    for _ in range(50):
        if t.processed_count >= 2:
            break
        time.sleep(0.01)
    t.pause()
    count_at_pause = t.processed_count
    time.sleep(0.05)
    assert t.processed_count == count_at_pause
    t.set_terminating()
    t.join(timeout=2)


def test_resume_restarts_processing():
    t = _CountingThread(interval_sec=0)
    t.start()
    t.pause()
    time.sleep(0.05)
    count_before = t.processed_count
    t.resume()
    for _ in range(50):
        if t.processed_count > count_before:
            break
        time.sleep(0.01)
    t.set_terminating()
    t.join(timeout=2)
    assert t.processed_count > count_before


def test_wake_up_interrupts_long_interval():
    t = _CountingThread(interval_sec=60)
    t.start()
    # Give it time to enter the wait.
    time.sleep(0.05)
    start = time.monotonic()
    t.set_terminating()
    t.join(timeout=2)
    assert time.monotonic() - start < 2


def test_invalid_interval_raises():
    with pytest.raises(AssertionError):
        _CountingThread(interval_sec=-1)


def test_invalid_error_notifier_raises():
    with pytest.raises(AssertionError):
        _CountingThread(error_notifier="not_callable")


def test_name_forwarded_to_thread():
    t = _CountingThread(name="my-thread")
    assert t.name == "my-thread"
    assert t._log_name == "my-thread"
