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

from aioservicekit.services import Service, ServiceState, service


class MockService(Service):
    def __init__(self, work_mock=None, **kwargs):
        super().__init__(**kwargs)
        self.work_mock = work_mock or AsyncMock()

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


async def test_initial_state():
    svc = MockService()
    assert svc.state == ServiceState.STOPED
    assert svc.name == "MockService"
    assert not svc.run


async def test_custom_name():
    svc = MockService(name="CustomName")
    assert svc.name == "CustomName"


async def test_basic_lifecycle():
    svc = MockService()
    state_change_listener = AsyncMock()
    svc.on_state_change.add_listener(state_change_listener)

    # Test start
    await svc.start()
    assert svc.state == ServiceState.RUNNING
    assert svc.run
    state_change_listener.assert_has_calls(
        [call(ServiceState.STARTING), call(ServiceState.RUNNING)]
    )

    # Test stop
    svc.stop()
    await svc.wait()
    assert svc.state == ServiceState.STOPED
    assert not svc.run
    state_change_listener.assert_has_calls(
        [call(ServiceState.STOPING), call(ServiceState.STOPED)]
    )


async def test_service_with_dependencies():
    dep_service = MockService(name="Dependency")
    main_service = MockService(name="Main", dependences=[dep_service])

    # Start main service which should start dependency
    await main_service.start()

    assert dep_service.state == ServiceState.RUNNING
    assert main_service.state == ServiceState.RUNNING

    # Stop dependency should trigger main service stop
    dep_service.stop()
    await main_service.wait()

    assert dep_service.state == ServiceState.STOPED
    assert main_service.state == ServiceState.STOPED


async def test_service_error_handling():
    error_mock = AsyncMock()
    svc = MockService()
    svc.on_error.add_listener(error_mock)

    # Setup work to raise an exception
    exception = Exception("Test error")
    svc.work_mock.side_effect = exception

    await svc.start()
    await asyncio.sleep(0.1)  # Allow error to propagate

    # Error should be emitted
    error_mock.assert_awaited_with(exception)
    svc.stop()


async def test_function_service():
    work_mock = AsyncMock()

    @service()
    async def test_service():
        await work_mock()
        await asyncio.sleep(0)

    svc = test_service()
    await svc.start()
    await asyncio.sleep(0.1)

    assert work_mock.called

    svc.stop()
    await svc.wait()


async def test_service_hooks():
    start_mock = AsyncMock()
    stop_mock = AsyncMock()

    class HookedService(MockService):
        def __on_start__(self):
            return start_mock()

        def __on_stop__(self):
            return stop_mock()

    svc = HookedService()
    await svc.start()
    start_mock.assert_awaited_once()

    svc.stop()
    await svc.wait()
    stop_mock.assert_awaited_once()


async def test_task_creation():
    svc = MockService()
    await svc.start()

    task_mock = AsyncMock()
    task = svc.create_task(task_mock())

    assert isinstance(task, asyncio.Task)
    await asyncio.sleep(0.1)
    task_mock.assert_awaited_once()

    svc.stop()
    await svc.wait()


async def test_service_restart():
    svc = MockService()
    await svc.start()
    assert svc.state == ServiceState.RUNNING

    await svc.restart()
    assert svc.state == ServiceState.RUNNING

    svc.stop()
    await svc.wait()


async def test_service_restart_when_not_running():
    svc = MockService()
    await svc.restart()
    assert svc.state == ServiceState.RUNNING

    svc.stop()
    await svc.wait()


async def test_service_stop_when_not_running():
    svc = MockService()
    svc.stop()
    await asyncio.sleep(0.01)
    assert svc.state == ServiceState.STOPED


async def test_service_start_when_already_started():
    svc = MockService()
    await svc.start()
    await svc.start()
    assert svc.state == ServiceState.RUNNING

    svc.stop()
    await svc.wait()


async def test_service_on_shutdown_hook():
    from aioservicekit.events import on_shutdown as on_shutdown_event

    svc = MockService()
    await svc.start()

    await on_shutdown_event().emit(SIGINT)

    await svc.wait()
    assert svc.state == ServiceState.STOPED


async def test_service_with_already_running_dependencies():
    dep1 = MockService(name="Dep1")
    dep2 = MockService(name="Dep2")

    await dep1.start()
    await dep2.start()

    main_svc = MockService(name="Main", dependences=[dep1, dep2])
    await main_svc.start()

    assert main_svc.state == ServiceState.RUNNING
    assert dep1.state == ServiceState.RUNNING
    assert dep2.state == ServiceState.RUNNING

    main_svc.stop()
    await main_svc.wait()

    dep1.stop()
    dep2.stop()


async def test_service_work_completes_triggers_stop():
    class ShortLivedService(MockService):
        async def __work__(self):
            await self.work_mock()
            self.stop()

    svc = ShortLivedService()
    await svc.start()
    await svc.wait()

    assert svc.state == ServiceState.STOPED


async def test_function_service_with_name():
    @service(name="CustomServiceName")
    async def test_service():
        await asyncio.sleep(0.01)

    svc = test_service()
    assert svc.name == "CustomServiceName"
    await svc.start()
    await asyncio.sleep(0.05)  # Wait for one cycle
    svc.stop()
    await svc.wait()
    assert svc.state == ServiceState.STOPED


async def test_function_service_with_dependencies():
    dep = MockService(name="Dep")

    @service(dependences=[dep])
    async def test_service():
        await asyncio.sleep(0.1)

    svc = test_service()
    await svc.start()

    assert dep.state == ServiceState.RUNNING

    svc.stop()
    await svc.wait()


async def test_service_create_task_uncancelable():
    svc = MockService()
    await svc.start()

    task_mock = AsyncMock()
    task = svc.create_task(task_mock(), canceliable=False)

    assert isinstance(task, asyncio.Task)
    await asyncio.sleep(0.1)
    task_mock.assert_awaited_once()

    svc.stop()
    await svc.wait()


async def test_service_wait_when_stopped():
    svc = MockService()
    await svc.wait()


async def test_service_on_error_listener():
    error_handler = AsyncMock()
    svc = MockService()
    svc.on_error.add_listener(error_handler)

    exception = ValueError("test error")
    svc.work_mock.side_effect = exception

    await svc.start()
    await asyncio.sleep(0.1)

    error_handler.assert_called()
    assert isinstance(error_handler.call_args[0][0], ValueError)

    svc.stop()
    await svc.wait()


async def test_service_main_task_error_during_stop():
    """Test error handling during main task cleanup in __stop__"""

    class ServiceWithBadWork(Service):
        def __init__(self, **kwargs):
            super().__init__(**kwargs)
            self.work_count = 0

        async def __work__(self):
            self.work_count += 1
            if self.work_count == 1:
                self.stop()
            await asyncio.sleep(0.01)

    svc = ServiceWithBadWork(name="BadWork")
    await svc.start()
    await svc.wait()
    assert svc.state == ServiceState.STOPED
