"""Supplemental tests for pyos/CentralDispatch.py — gaps in existing coverage."""

import time

import pytest

from pyos.CentralDispatch import (
    AppShutDownSignal,
    CentralDispatch,
    ConcurrentDispatchQueue,
    SerialDispatchQueue,
    perform_on,
    wrap_with_try,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _identity_handler(func):
    """Pass-through exception handler for testing."""
    return func


# ===========================================================================
# ConcurrentDispatchQueue
# ===========================================================================

class TestConcurrentDispatchQueue:
    def test_await_result_returns_value(self):
        q = ConcurrentDispatchQueue(2, _identity_handler)
        try:
            result = q.await_result(lambda: 42)
            assert result == 42
        finally:
            q.task_threadpool.shutdown(wait=False)

    def test_await_result_propagates_exception(self):
        q = ConcurrentDispatchQueue(2, _identity_handler)
        try:
            with pytest.raises(ValueError, match="boom"):
                q.await_result(_raise_value_error)
        finally:
            q.task_threadpool.shutdown(wait=False)

    def test_finish_work_waits_for_pending(self):
        q = ConcurrentDispatchQueue(2, _identity_handler)
        results = []

        def slow_task():
            time.sleep(0.05)
            results.append("done")

        q.submit_async(slow_task)
        q.finish_work().result(timeout=5)
        assert "done" in results
        q.task_threadpool.shutdown(wait=False)


def _raise_value_error():
    raise ValueError("boom")


# ===========================================================================
# SerialDispatchQueue.await_result
# ===========================================================================

class TestSerialDispatchQueueAwait:
    def test_await_result_blocks_and_returns(self):
        q = SerialDispatchQueue(_identity_handler)
        try:
            result = q.await_result(lambda: "hello")
            assert result == "hello"
        finally:
            q.task_threadpool.shutdown(wait=False)

    def test_await_result_propagates_exception(self):
        q = SerialDispatchQueue(_identity_handler)
        try:
            with pytest.raises(ValueError):
                q.await_result(_raise_value_error)
        finally:
            q.task_threadpool.shutdown(wait=False)


# ===========================================================================
# perform_on
# ===========================================================================

class TestPerformOn:
    def test_sync_blocks_and_returns(self):
        q = SerialDispatchQueue(_identity_handler)
        try:
            wrapped = perform_on(lambda a, b: a + b, q, do_async=False)
            assert wrapped(3, 4) == 7
        finally:
            q.task_threadpool.shutdown(wait=False)

    def test_async_returns_future(self):
        q = SerialDispatchQueue(_identity_handler)
        try:
            wrapped = perform_on(lambda: 99, q, do_async=True)
            future = wrapped()
            assert future.result(timeout=5) == 99
        finally:
            q.task_threadpool.shutdown(wait=False)

    def test_exception_propagates_sync(self):
        q = SerialDispatchQueue(_identity_handler)
        try:
            wrapped = perform_on(_raise_value_error, q, do_async=False)
            with pytest.raises(ValueError, match="boom"):
                wrapped()
        finally:
            q.task_threadpool.shutdown(wait=False)


# ===========================================================================
# wrap_with_try
# ===========================================================================

class TestWrapWithTry:
    def test_returns_value_transparently(self):
        @wrap_with_try
        def add(a, b):
            return a + b

        assert add(2, 3) == 5

    def test_reraises_exceptions(self):
        @wrap_with_try
        def fail():
            raise RuntimeError("bad")

        with pytest.raises(RuntimeError, match="bad"):
            fail()


# ===========================================================================
# AppShutDownSignal
# ===========================================================================

class TestAppShutDownSignal:
    def test_is_distinct_type(self):
        sig = AppShutDownSignal()
        assert isinstance(sig, AppShutDownSignal)

    def test_not_instance_of_exception(self):
        sig = AppShutDownSignal()
        assert not isinstance(sig, Exception)
