import asyncio
from unittest.mock import AsyncMock

from pytest import raises

from aioservicekit.services import Service
from aioservicekit.utils import main, run_services


class DummyService(Service):
    def __init__(self, name="Dummy", **kwargs):
        super().__init__(name=name, **kwargs)
        self.work_mock = AsyncMock()

    async def __work__(self):
        await self.work_mock()
        await asyncio.sleep(0.05)


async def test_main_decorator_no_background_tasks():
    @main
    async def main_coro():
        return 42

    result = await main_coro()
    assert result == 42


async def test_main_decorator_with_background_task():
    @main
    async def main_coro():
        await asyncio.sleep(0.1)
        return 42

    result = await main_coro()
    assert result == 42


async def test_main_decorator_awaits_background_tasks():
    background_task_executed = False

    async def background_coro():
        nonlocal background_task_executed
        await asyncio.sleep(0.05)
        background_task_executed = True

    @main
    async def main_coro():
        asyncio.create_task(background_coro())
        await asyncio.sleep(0)
        return 42

    result = await main_coro()
    assert result == 42
    assert background_task_executed


async def test_main_decorator_multiple_background_tasks():
    counter = 0

    async def background_coro(id):
        nonlocal counter
        await asyncio.sleep(0.05)
        counter += id

    @main
    async def main_coro():
        for i in range(3):
            asyncio.create_task(background_coro(i))
        await asyncio.sleep(0)
        return 42

    result = await main_coro()
    assert result == 42
    assert counter == 3  # 0 + 1 + 2


async def test_run_services_empty_list():
    async with run_services([]) as waiter:
        assert waiter is not None
        await asyncio.sleep(0.01)


async def test_run_services_single_service():
    svc = DummyService(name="TestService")

    async with run_services([svc]):
        assert svc.state.value == 2  # RUNNING
        await asyncio.sleep(0.1)
        assert svc.work_mock.called

    await svc.wait()
    assert svc.state.value == 4  # STOPED


async def test_run_services_multiple_services():
    svc1 = DummyService(name="Service1")
    svc2 = DummyService(name="Service2")

    async with run_services([svc1, svc2]):
        assert svc1.state.value == 2  # RUNNING
        assert svc2.state.value == 2  # RUNNING
        await asyncio.sleep(0.1)
        assert svc1.work_mock.called
        assert svc2.work_mock.called

    await svc1.wait()
    await svc2.wait()
    assert svc1.state.value == 4  # STOPED
    assert svc2.state.value == 4  # STOPED


async def test_run_services_starts_in_order():
    svc1 = DummyService(name="Service1")
    svc2 = DummyService(name="Service2")

    async with run_services([svc1, svc2]):
        pass

    await svc1.wait()
    await svc2.wait()
    # Services should have been started
    assert svc1.state.value in [2, 4]  # RUNNING or STOPED
    assert svc2.state.value in [2, 4]


async def test_run_services_stops_on_exception():
    svc = DummyService(name="TestService")

    try:
        async with run_services([svc]):
            raise ValueError("Test exception")
    except ValueError:
        pass

    # Service should be stopped after exception in context
    await svc.wait()
    assert svc.state.value == 4  # STOPED


async def test_run_services_service_start_exception():
    class FailingService(Service):
        def __init__(self, **kwargs):
            super().__init__(**kwargs)

        async def __work__(self):
            await asyncio.sleep(10)

        async def __on_start__(self):
            raise ValueError("Start failure")

    svc = FailingService(name="FailingService")

    with raises(ValueError):
        async with run_services([svc]):
            pass


async def test_run_services_context_manager_returns():
    svc = DummyService(name="TestService")

    async with run_services([svc]) as waiter:
        # waiter should be awaitable
        import inspect

        assert inspect.isawaitable(waiter)
