import asyncio
from signal import SIGINT
from unittest.mock import AsyncMock, call

from pytest import raises

from aioservicekit import Event, EventClosedError, on_shutdown, on_signal


async def test_initial_state():
    event = Event()
    assert len(event.__listeners__) == 0


async def test_listeners():
    event = Event()
    mock_callback = AsyncMock()

    event.add_listener(mock_callback)
    assert len(event.__listeners__) == 1
    assert mock_callback in event.__listeners__

    event.remove_listener(mock_callback)
    assert len(event.__listeners__) == 0
    assert mock_callback not in event.__listeners__


async def test_emit_single_listener():
    event = Event()
    mock_callback = AsyncMock()

    event.add_listener(mock_callback)
    await event.emit(42)

    mock_callback.assert_awaited_once_with(42)


async def test_emit_multiple_listeners():
    event = Event()
    mock_callbacks = [AsyncMock() for _ in range(3)]

    for callback in mock_callbacks:
        event.add_listener(callback)

    await event.emit(42)

    for callback in mock_callbacks:
        callback.assert_awaited_once_with(42)


async def test_emit_different_values():
    event = Event()
    mock_callback = AsyncMock()

    event.add_listener(mock_callback)

    test_values = [1, 2, 3]
    for value in test_values:
        await event.emit(value)

    assert mock_callback.await_count == len(test_values)
    mock_callback.assert_has_awaits([call(value) for value in test_values])


async def test_emit_with_no_listeners():
    event = Event()
    # Should not raise any exceptions
    await event.emit(42)


async def test_listener_exception_handling():
    event = Event()

    async def failing_callback():
        raise ValueError("Test exception")

    def failing_sync_callback():
        raise ValueError("Test exception")

    mock_success = AsyncMock()

    event.add_listener(failing_callback)
    event.add_listener(failing_sync_callback)
    event.add_listener(mock_success)

    # Should not raise exception and should continue to other listeners
    await event.emit(42)

    mock_success.assert_awaited_once_with(42)


async def test_event_call_emit():
    event = Event()
    mock_callback = AsyncMock()
    event.add_listener(mock_callback)

    await event(42)

    mock_callback.assert_awaited_once_with(42)


async def test_event_context_manager_sync():
    event = Event()
    mock_callback = AsyncMock()
    event.add_listener(mock_callback)

    with event:
        pass

    assert event.is_closed
    assert len(event.__listeners__) == 0


async def test_event_context_manager_async():
    event = Event()
    mock_callback = AsyncMock()
    event.add_listener(mock_callback)

    async with event:
        pass

    assert event.is_closed
    assert len(event.__listeners__) == 0


async def test_event_context_manager_with_exception():
    event = Event()

    with raises(ValueError):
        with event:
            raise ValueError("test")

    assert event.is_closed


async def test_event_context_manager_async_with_exception():
    event = Event()

    with raises(ValueError):
        async with event:
            raise ValueError("test")

    assert event.is_closed


async def test_event_len():
    event = Event()
    assert len(event) == 0

    cb1 = AsyncMock()
    cb2 = AsyncMock()
    event.add_listener(cb1)
    event.add_listener(cb2)

    assert len(event) == 2


async def test_event_is_closed_property():
    event = Event()
    assert event.is_closed is False

    event.close()
    assert event.is_closed is True


async def test_event_add_listener_after_close():
    event = Event()
    event.close()

    with raises(EventClosedError):
        event.add_listener(AsyncMock())


async def test_event_emit_after_close():
    event = Event()
    event.close()

    with raises(EventClosedError):
        await event.emit(42)


async def test_event_iadd():
    event = Event()
    mock_callback = AsyncMock()

    event.__iadd__(mock_callback)
    assert mock_callback in event.__listeners__


async def test_event_isub():
    event = Event()
    mock_callback = AsyncMock()
    event.add_listener(mock_callback)

    event.__isub__(mock_callback)
    assert mock_callback not in event.__listeners__


async def test_event_has_listener():
    event = Event()
    mock_callback = AsyncMock()

    assert not event.has_listener(mock_callback)
    event.add_listener(mock_callback)
    assert event.has_listener(mock_callback)


async def test_event_contains():
    event = Event()
    mock_callback = AsyncMock()

    assert mock_callback not in event
    event.add_listener(mock_callback)
    assert mock_callback in event


async def test_event_close_idempotent():
    event = Event()
    event.close()
    event.close()
    assert event.is_closed


async def test_event_non_callable_listener():
    event = Event()

    def not_a_callable():
        pass

    event.add_listener(not_a_callable)
    assert len(event) == 1


async def test_on_signal_singleton():
    signal_event1 = on_signal()
    signal_event2 = on_signal()

    assert signal_event1 is signal_event2


async def test_on_signal_emits():
    signal_event = on_signal()
    mock_callback = AsyncMock()
    signal_event.add_listener(mock_callback)

    await signal_event.emit(SIGINT)

    mock_callback.assert_awaited_once_with(SIGINT)


async def test_on_shutdown_singleton():
    shutdown_event1 = on_shutdown()
    shutdown_event2 = on_shutdown()

    assert shutdown_event1 is shutdown_event2


async def test_on_shutdown_filters_signals():
    shutdown_event = on_shutdown()
    mock_callback = AsyncMock()
    shutdown_event.add_listener(mock_callback)

    await shutdown_event.emit(SIGINT)

    mock_callback.assert_awaited_once_with(SIGINT)


async def test_event_emit_task_exception():
    """Test that exceptions in emit tasks are handled"""
    event = Event()

    async def failing_listener():
        raise ValueError("Listener error")

    event.add_listener(failing_listener)
    # Should not raise
    await event.emit(42)
    await asyncio.sleep(0.1)
