"""
Тесты для новой архитектуры событий

Проверяет:
- EventBus (pub/sub)
- EventScheduler (независимый планировщик)
- EventDispatcher (обработчик событий с контекстом)
"""

import asyncio
from unittest.mock import AsyncMock, MagicMock

import pytest

from ui_router.state.context import EventMode, ExecutionContext, NavigationManager, NavigationState
from ui_router.events.dispatcher import EventDispatcher
from ui_router.events import EventBus, EventData, EventSourceType, InMemoryEventScheduler
from ui_router.state.storage import InMemoryNavigationStorage
from ui_router.schema import EventHandler, UIRouter, Scene, SendMessageAction, MessageContent


class TestEventBus:
    """Тесты для EventBus - pub/sub шины"""

    @pytest.mark.asyncio
    async def test_subscribe_and_emit(self):
        """EventBus должен вызывать подписчиков при emit"""
        bus = EventBus()
        callback = AsyncMock()

        # Подписываемся на событие
        bus.subscribe("test_event", callback)

        # Эмитим событие
        event = EventData(
            event_name="test_event",
            source_type=EventSourceType.INTERNAL,
            data={"foo": "bar"},
            bot_id=12345,
            user_id=67890,
        )
        await bus.emit(event)

        # Проверяем что callback был вызван
        callback.assert_called_once_with(event)

    @pytest.mark.asyncio
    async def test_multiple_subscribers(self):
        """EventBus должен вызывать всех подписчиков"""
        bus = EventBus()
        callback1 = AsyncMock()
        callback2 = AsyncMock()

        bus.subscribe("test_event", callback1)
        bus.subscribe("test_event", callback2)

        event = EventData(
            event_name="test_event",
            source_type=EventSourceType.INTERNAL,
            data={},
            bot_id=1,
        )
        await bus.emit(event)

        callback1.assert_called_once()
        callback2.assert_called_once()

    @pytest.mark.asyncio
    async def test_unsubscribe(self):
        """Unsubscribe должен удалять подписчика"""
        bus = EventBus()
        callback = AsyncMock()

        bus.subscribe("test_event", callback)
        bus.unsubscribe("test_event", callback)

        event = EventData(
            event_name="test_event",
            source_type=EventSourceType.INTERNAL,
            data={},
            bot_id=1,
        )
        await bus.emit(event)

        # Callback не должен быть вызван
        callback.assert_not_called()


class TestEventScheduler:
    """Тесты для InMemoryEventScheduler"""

    @pytest.mark.asyncio
    async def test_schedule_once(self):
        """schedule_once должен выполнить событие с задержкой"""
        callback = AsyncMock()
        scheduler = InMemoryEventScheduler(event_callback=callback)

        # Планируем событие с задержкой 1 секунда
        task_id = await scheduler.schedule_once(
            event_name="delayed_event",
            delay="1s",
            event_data={"test": "data"},
            bot_id=123,
            user_id=456,
        )

        assert task_id is not None
        assert task_id in scheduler._tasks

        # Ждём выполнения
        await asyncio.sleep(1.2)

        # Callback должен быть вызван
        callback.assert_called_once()
        call_args = callback.call_args[0][0]
        assert isinstance(call_args, EventData)
        assert call_args.event_name == "delayed_event"
        assert call_args.data == {"test": "data"}
        assert call_args.source_type == EventSourceType.SCHEDULED

    @pytest.mark.asyncio
    async def test_cancel_scheduled(self):
        """cancel_scheduled должен отменить запланированное событие"""
        callback = AsyncMock()
        scheduler = InMemoryEventScheduler(event_callback=callback)

        task_id = await scheduler.schedule_once(
            event_name="test_event",
            delay="2s",
            event_data={},
            bot_id=1,
        )

        # Отменяем до выполнения
        result = await scheduler.cancel_scheduled(task_id)
        assert result is True

        # Ждём и проверяем что callback не был вызван
        await asyncio.sleep(2.5)
        callback.assert_not_called()

    @pytest.mark.asyncio
    async def test_scheduler_with_eventbus(self):
        """EventScheduler должен работать с EventBus через callback"""
        bus = EventBus()
        subscriber = AsyncMock()
        bus.subscribe("scheduled_event", subscriber)

        # Создаём scheduler с EventBus.emit как callback
        scheduler = InMemoryEventScheduler(event_callback=bus.emit)

        # Планируем событие
        await scheduler.schedule_once(
            event_name="scheduled_event",
            delay="1s",
            event_data={"key": "value"},
            bot_id=999,
            user_id=111,
        )

        # Ждём выполнения
        await asyncio.sleep(1.2)

        # Subscriber должен получить событие через EventBus
        subscriber.assert_called_once()
        event = subscriber.call_args[0][0]
        assert event.event_name == "scheduled_event"
        assert event.data == {"key": "value"}


class TestEventDispatcher:
    """Тесты для EventDispatcher"""

    @pytest.fixture
    def navigation_manager(self):
        """Фикстура для NavigationManager"""
        storage = InMemoryNavigationStorage()
        return NavigationManager(navigation_storage=storage)

    @pytest.fixture
    def mock_router_executor(self, navigation_manager):
        """Mock UIRouterExecutor"""
        from .test_services import NavigationService

        router = MagicMock()
        router.navigation_manager = navigation_manager
        router.navigation_service = NavigationService(navigation_manager=navigation_manager)
        router.schema = UIRouter(
            name="test_schema",
            initial_scene="start",
            scenes=[
                Scene(id="start", name="Start", default_content=None, handlers=[])
            ],
            event_handlers=[
                EventHandler(
                    event_name="test_event",
                    actions=[
                        SendMessageAction(
                            content=MessageContent(text="Event handled!"),
                        )
                    ],
                )
            ],
        )
        router.shared = MagicMock()
        router.shared.variable_repository = MagicMock()
        router.shared.variable_repository.get = AsyncMock(return_value=None)
        router.variable_repository = MagicMock()
        router.variable_repository.get = AsyncMock(return_value=None)
        router.rule_engine = MagicMock()
        router.rule_engine._get_variable_value = AsyncMock(return_value=None)

        # Mock context_factory
        router.context_factory = MagicMock()
        async def mock_create_context(**kwargs):
            """Mock create_context возвращает ExecutionContext"""
            from aiogram import Bot
            bot = kwargs.get('bot') or MagicMock(spec=Bot)
            bot.id = 123
            return ExecutionContext(
                navigation_manager=navigation_manager,
                navigation_state=kwargs.get('navigation_state'),
                scene_id=kwargs.get('scene_id'),
                bot=bot,
                user_id=kwargs.get('user_id'),
                chat_id=kwargs.get('chat_id'),
                event=kwargs.get('event'),
                event_mode=kwargs.get('event_mode', EventMode.MESSAGE),
                event_data=kwargs.get('event_data', {}),
            )
        router.context_factory.create_context = mock_create_context

        return router

    @pytest.fixture
    def mock_action_executor(self):
        """Mock ActionExecutor"""
        executor = MagicMock()
        executor.execute = AsyncMock()
        return executor

    @pytest.mark.asyncio
    async def test_dispatch_event(self, mock_router_executor, mock_action_executor, navigation_manager):
        """EventDispatcher должен обрабатывать событие"""
        # Mock flag_resolver
        flag_resolver = MagicMock()
        flag_resolver.resolve_scene_flags = AsyncMock()

        dispatcher = EventDispatcher(
            schema=mock_router_executor.schema,
            navigation_manager=navigation_manager,
            action_executor=mock_action_executor,
            flag_resolver=flag_resolver,
            variable_repository=mock_router_executor.shared.variable_repository,
            rule_engine=mock_router_executor.rule_engine,
            context_factory=mock_router_executor.context_factory,
        )

        # Инициализируем навигацию для пользователя
        await navigation_manager.initialize(
            bot_id=123,
            user_id=456,
            chat_id=456,
            initial_scene="start",
        )

        # Создаём событие
        event = EventData(
            event_name="test_event",
            source_type=EventSourceType.INTERNAL,
            data={"param": "value"},
            bot_id=123,
            user_id=456,
            chat_id=456,
        )

        # Обрабатываем событие
        await dispatcher.dispatch(event, bot=None)

        # ActionExecutor.execute должен быть вызван для action
        mock_action_executor.execute.assert_called_once()

    @pytest.mark.asyncio
    async def test_dispatch_without_user_id(self, mock_router_executor, mock_action_executor):
        """EventDispatcher должен пропускать события без user_id для INTERNAL/WEBHOOK"""
        # Mock flag_resolver
        flag_resolver = MagicMock()
        flag_resolver.resolve_scene_flags = AsyncMock()

        # Mock navigation_manager
        navigation_manager = MagicMock()

        dispatcher = EventDispatcher(
            schema=mock_router_executor.schema,
            navigation_manager=navigation_manager,
            action_executor=mock_action_executor,
            flag_resolver=flag_resolver,
            variable_repository=mock_router_executor.shared.variable_repository,
            rule_engine=mock_router_executor.rule_engine,
            context_factory=mock_router_executor.context_factory,
        )

        # Событие без user_id
        event = EventData(
            event_name="test_event",
            source_type=EventSourceType.INTERNAL,
            data={},
            bot_id=123,
            user_id=None,
        )

        await dispatcher.dispatch(event, bot=None)

        # ActionExecutor не должен быть вызван
        mock_action_executor.execute.assert_not_called()

    @pytest.mark.asyncio
    async def test_event_mode_detection(self, mock_router_executor, mock_action_executor, navigation_manager):
        """EventDispatcher должен определять EventMode по source_type"""
        # Mock flag_resolver
        flag_resolver = MagicMock()
        flag_resolver.resolve_scene_flags = AsyncMock()

        dispatcher = EventDispatcher(
            schema=mock_router_executor.schema,
            navigation_manager=navigation_manager,
            action_executor=mock_action_executor,
            flag_resolver=flag_resolver,
            variable_repository=mock_router_executor.shared.variable_repository,
            rule_engine=mock_router_executor.rule_engine,
            context_factory=mock_router_executor.context_factory,
        )

        # Инициализируем навигацию
        await navigation_manager.initialize(
            bot_id=123,
            user_id=456,
            chat_id=456,
            initial_scene="start",
        )

        # Тестируем SCHEDULED event
        scheduled_event = EventData(
            event_name="test_event",
            source_type=EventSourceType.SCHEDULED,
            data={},
            bot_id=123,
            user_id=456,
        )

        await dispatcher.dispatch(scheduled_event, bot=None)

        # Проверяем что context создался с EventMode.SCHEDULED
        call_args = mock_action_executor.execute.call_args
        if call_args:
            context = call_args[0][1]
            assert isinstance(context, ExecutionContext)
            assert context.event_mode == EventMode.SCHEDULED


class TestIntegration:
    """Интеграционные тесты всей архитектуры"""

    @pytest.mark.asyncio
    async def test_full_event_flow(self):
        """Полный flow: EventBus -> EventDispatcher -> ActionExecutor"""
        # Подготовка
        storage = InMemoryNavigationStorage()
        nav_manager = NavigationManager(navigation_storage=storage)

        # Инициализируем навигацию для пользователя
        await nav_manager.initialize(
            bot_id=123,
            user_id=456,
            chat_id=456,
            initial_scene="start",
        )

        # Mock router executor
        from .test_services import NavigationService

        router = MagicMock()
        router.navigation_manager = nav_manager
        router.navigation_service = NavigationService(navigation_manager=nav_manager)
        router.schema = UIRouter(
            name="test",
            initial_scene="start",
            scenes=[
                Scene(id="start", name="Start", default_content=None, handlers=[])
            ],
            event_handlers=[
                EventHandler(
                    event_name="user_registered",
                    actions=[
                        SendMessageAction(
                            content=MessageContent(text="Welcome!"),
                        )
                    ],
                )
            ],
        )
        router.shared = MagicMock()
        router.shared.variable_repository = MagicMock()
        router.shared.variable_repository.get = AsyncMock(return_value=None)
        router.variable_repository = MagicMock()
        router.variable_repository.get = AsyncMock(return_value=None)
        router.rule_engine = MagicMock()
        router.rule_engine._get_variable_value = AsyncMock(return_value=None)

        # Mock context_factory
        router.context_factory = MagicMock()
        async def mock_create_context(**kwargs):
            from aiogram import Bot
            bot = kwargs.get('bot') or MagicMock(spec=Bot)
            bot.id = 123
            return ExecutionContext(
                navigation_manager=nav_manager,
                navigation_state=kwargs.get('navigation_state'),
                scene_id=kwargs.get('scene_id'),
                bot=bot,
                user_id=kwargs.get('user_id'),
                chat_id=kwargs.get('chat_id'),
                event=kwargs.get('event'),
                event_mode=kwargs.get('event_mode', EventMode.MESSAGE),
                event_data=kwargs.get('event_data', {}),
            )
        router.context_factory.create_context = mock_create_context

        # Mock action executor
        action_executor = MagicMock()
        action_executor.execute = AsyncMock()

        # Создаём компоненты
        event_bus = EventBus()

        # Mock flag_resolver
        flag_resolver = MagicMock()
        flag_resolver.resolve_scene_flags = AsyncMock()

        dispatcher = EventDispatcher(
            schema=router.schema,
            navigation_manager=nav_manager,
            action_executor=action_executor,
            flag_resolver=flag_resolver,
            variable_repository=router.shared.variable_repository,
            rule_engine=router.rule_engine,
            context_factory=router.context_factory,
        )

        # Подписываем dispatcher на событие
        event_bus.subscribe("user_registered", dispatcher.dispatch)

        # Эмитим событие
        event = EventData(
            event_name="user_registered",
            source_type=EventSourceType.INTERNAL,
            data={"username": "john"},
            bot_id=123,
            user_id=456,
            chat_id=456,
        )

        await event_bus.emit(event)

        # Проверяем что action был выполнен
        action_executor.execute.assert_called_once()

    @pytest.mark.asyncio
    async def test_scheduled_event_flow(self):
        """Полный flow: EventScheduler -> EventBus -> EventDispatcher"""
        # Подготовка
        storage = InMemoryNavigationStorage()
        nav_manager = NavigationManager(navigation_storage=storage)

        await nav_manager.initialize(
            bot_id=123,
            user_id=456,
            chat_id=456,
            initial_scene="start",
        )

        # Mock router
        from .test_services import NavigationService

        router = MagicMock()
        router.navigation_manager = nav_manager
        router.navigation_service = NavigationService(navigation_manager=nav_manager)
        router.schema = UIRouter(
            name="test",
            initial_scene="start",
            scenes=[
                Scene(id="start", name="Start", default_content=None, handlers=[])
            ],
            event_handlers=[
                EventHandler(
                    event_name="reminder",
                    actions=[
                        SendMessageAction(
                            content=MessageContent(text="Reminder!"),
                        )
                    ],
                )
            ],
        )
        router.shared = MagicMock()
        router.shared.variable_repository = MagicMock()
        router.shared.variable_repository.get = AsyncMock(return_value=None)
        router.variable_repository = MagicMock()
        router.variable_repository.get = AsyncMock(return_value=None)
        router.rule_engine = MagicMock()
        router.rule_engine._get_variable_value = AsyncMock(return_value=None)

        # Mock context_factory
        router.context_factory = MagicMock()
        async def mock_create_context(**kwargs):
            from aiogram import Bot
            bot = kwargs.get('bot') or MagicMock(spec=Bot)
            bot.id = 123
            return ExecutionContext(
                navigation_manager=nav_manager,
                navigation_state=kwargs.get('navigation_state'),
                scene_id=kwargs.get('scene_id'),
                bot=bot,
                user_id=kwargs.get('user_id'),
                chat_id=kwargs.get('chat_id'),
                event=kwargs.get('event'),
                event_mode=kwargs.get('event_mode', EventMode.MESSAGE),
                event_data=kwargs.get('event_data', {}),
            )
        router.context_factory.create_context = mock_create_context

        action_executor = MagicMock()
        action_executor.execute = AsyncMock()

        # Создаём компоненты
        event_bus = EventBus()

        # Mock flag_resolver
        flag_resolver = MagicMock()
        flag_resolver.resolve_scene_flags = AsyncMock()

        dispatcher = EventDispatcher(
            schema=router.schema,
            navigation_manager=nav_manager,
            action_executor=action_executor,
            flag_resolver=flag_resolver,
            variable_repository=router.shared.variable_repository,
            rule_engine=router.rule_engine,
            context_factory=router.context_factory,
        )
        scheduler = InMemoryEventScheduler(event_callback=event_bus.emit)

        # Подписываем dispatcher
        event_bus.subscribe("reminder", dispatcher.dispatch)

        # Планируем событие
        await scheduler.schedule_once(
            event_name="reminder",
            delay="1s",
            event_data={"message": "Don't forget!"},
            bot_id=123,
            user_id=456,
            chat_id=456,
        )

        # Ждём выполнения
        await asyncio.sleep(1.2)

        # Проверяем что action был выполнен
        action_executor.execute.assert_called_once()
        call_args = action_executor.execute.call_args[0]
        context = call_args[1]

        # Проверяем что context имеет правильный event_mode
        assert context.event_mode == EventMode.SCHEDULED
        assert "message" in context.event_data
