import asyncio
from unittest.mock import AsyncMock

from aioservicekit.tasks import FnTask, Task, task


class MockTask(Task):
    def __init__(self, interval=0.1, **kwargs):
        super().__init__(interval, **kwargs)
        self.task_mock = AsyncMock()

    async def __task__(self):
        await self.task_mock()
        await asyncio.sleep(0)


async def test_task_initial_state():
    t = MockTask(interval=1.0, name="TestTask")
    assert t.interval == 1.0
    assert t.name == "TestTask"
    assert t.state.value == 4  # STOPED


async def test_task_interval_property():
    t = MockTask(interval=5.0)
    assert t.interval == 5.0


async def test_task_with_dependencies():
    from aioservicekit.services import Service

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

    dep = DummyService(name="Dep")
    t = MockTask(interval=0.1, dependences=[dep])

    await t.start()
    assert dep.state.value == 2  # RUNNING

    t.stop()
    await t.wait()


async def test_task_basic_lifecycle():
    t = MockTask(interval=0.05)
    error_handler = AsyncMock()
    t.on_error.add_listener(error_handler)

    await t.start()
    await asyncio.sleep(0.15)

    assert t.task_mock.called
    error_handler.assert_not_called()

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


async def test_task_work_loop_exception():
    """Test exception handling in work loop outside of TaskGroup"""
    call_count = 0

    class ExceptionInWork(Task):
        def __init__(self, interval=0.05, **kwargs):
            super().__init__(interval, **kwargs)

        async def __task__(self):
            nonlocal call_count
            call_count += 1
            if call_count > 1:
                raise RuntimeError("Work error")

        async def __work__(self):
            await self.__task__()
            await asyncio.sleep(self.interval)

    t = ExceptionInWork(interval=0.05)
    error_handler = AsyncMock()
    t.on_error.add_listener(error_handler)

    await t.start()
    await asyncio.sleep(0.3)

    assert error_handler.called

    t.stop()
    await t.wait()


async def test_fn_task():
    task_mock = AsyncMock()

    @task(interval=0.05)
    async def my_task():
        await task_mock()
        await asyncio.sleep(0)

    t = my_task()
    assert isinstance(t, FnTask)
    assert t.interval == 0.05

    await t.start()
    await asyncio.sleep(0.15)

    assert task_mock.called

    t.stop()
    await t.wait()


async def test_fn_task_with_args():
    task_mock = AsyncMock()

    @task(interval=0.3)
    async def my_task(arg1, arg2, kwarg1=None):
        await task_mock(arg1, arg2, kwarg1=kwarg1)
        await asyncio.sleep(0)

    t = my_task("val1", "val2", kwarg1="val3")
    await t.start()
    await asyncio.sleep(0.5)

    assert task_mock.called
    task_mock.assert_called_with("val1", "val2", kwarg1="val3")

    t.stop()
    await t.wait()


async def test_fn_task_with_dependencies():
    from aioservicekit.services import Service

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

    dep = DummyService(name="Dep")
    task_mock = AsyncMock()

    @task(interval=0.05, dependences=[dep])
    async def my_task():
        await task_mock()
        await asyncio.sleep(0)

    t = my_task()
    await t.start()
    assert dep.state.value == 2  # RUNNING
    await asyncio.sleep(0.15)
    assert task_mock.called

    t.stop()
    await t.wait()


async def test_task_multiple_cycles():
    call_count = 0

    class CountingTask(Task):
        def __init__(self, interval=0.05, **kwargs):
            super().__init__(interval, **kwargs)

        async def __task__(self):
            nonlocal call_count
            call_count += 1

    t = CountingTask(interval=0.05)
    await t.start()
    await asyncio.sleep(0.2)

    assert call_count >= 2

    t.stop()
    await t.wait()
