import asyncio

import pytest

from ui_router.events import EventData
from ui_router.exceptions import EventSchedulingError
from ui_router.integrations.apscheduler_scheduler import APSchedulerEventScheduler
from ui_router.schema import EventSourceType

from apscheduler.schedulers.asyncio import AsyncIOScheduler


@pytest.fixture
def captured_events():
    return []


@pytest.fixture
def event_callback(captured_events):
    async def callback(event: EventData):
        captured_events.append(event)

    return callback


@pytest.fixture
def scheduler(event_callback):
    ext = AsyncIOScheduler()
    s = APSchedulerEventScheduler(
        event_callback=event_callback,
        scheduler=ext,
        auto_start=True,
    )
    yield s
    try:
        if ext.running:
            ext.shutdown(wait=False)
    except RuntimeError:
        pass


class TestScheduleOnce:
    async def test_fires_callback(self, scheduler, captured_events):
        await scheduler.schedule_once("test_event", "1s", {"key": "val"}, bot_id=1, user_id=42, chat_id=100)
        await asyncio.sleep(1.5)
        assert len(captured_events) == 1
        event = captured_events[0]
        assert event.event_name == "test_event"
        assert event.source_type == EventSourceType.SCHEDULED
        assert event.data == {"key": "val"}
        assert event.bot_id == 1
        assert event.user_id == 42
        assert event.chat_id == 100

    async def test_returns_task_id(self, scheduler):
        task_id = await scheduler.schedule_once("ev", "5s", {}, bot_id=1)
        assert isinstance(task_id, str)
        assert len(task_id) > 0

    async def test_invalid_delay(self, scheduler):
        with pytest.raises(EventSchedulingError):
            await scheduler.schedule_once("ev", "bad", {}, bot_id=1)


class TestScheduleCron:
    async def test_registers_job(self, scheduler):
        task_id = await scheduler.schedule_cron("cron_ev", "* * * * *", {}, bot_id=1)
        assert isinstance(task_id, str)
        job = scheduler._scheduler.get_job(task_id)
        assert job is not None

    async def test_invalid_cron(self, scheduler):
        with pytest.raises(EventSchedulingError, match="Invalid cron expression"):
            await scheduler.schedule_cron("ev", "not_a_cron", {}, bot_id=1)


class TestScheduleInterval:
    async def test_fires_repeatedly(self, scheduler, captured_events):
        await scheduler.schedule_interval("int_ev", 1, {"n": 1}, bot_id=1)
        await asyncio.sleep(2.5)
        assert len(captured_events) >= 2

    async def test_returns_task_id(self, scheduler):
        task_id = await scheduler.schedule_interval("ev", 60, {}, bot_id=1)
        assert isinstance(task_id, str)


class TestCancelScheduled:
    async def test_cancel_prevents_firing(self, scheduler, captured_events):
        task_id = await scheduler.schedule_once("ev", "2s", {}, bot_id=1)
        result = await scheduler.cancel_scheduled(task_id)
        assert result is True
        await asyncio.sleep(2.5)
        assert len(captured_events) == 0

    async def test_cancel_nonexistent(self, scheduler):
        result = await scheduler.cancel_scheduled("nonexistent_id")
        assert result is False


class TestEventData:
    async def test_event_fields(self, scheduler, captured_events):
        await scheduler.schedule_once(
            "full_ev", "1s", {"a": 1, "b": "two"}, bot_id="bot_123", user_id=777, chat_id=888
        )
        await asyncio.sleep(1.5)
        assert len(captured_events) == 1
        event = captured_events[0]
        assert event.event_name == "full_ev"
        assert event.data == {"a": 1, "b": "two"}
        assert event.bot_id == "bot_123"
        assert event.user_id == 777
        assert event.chat_id == 888
        assert event.source_type == EventSourceType.SCHEDULED
        assert event.timestamp is not None


class TestShutdown:
    async def test_shutdown_stops_scheduler(self, event_callback):
        ext = AsyncIOScheduler()
        s = APSchedulerEventScheduler(
            event_callback=event_callback,
            scheduler=ext,
            auto_start=True,
        )
        await s.schedule_once("ev", "10s", {}, bot_id=1)
        assert ext.running
        s.shutdown(wait=False)
        await asyncio.sleep(0.1)
        assert not ext.running


class TestAutoStart:
    async def test_no_auto_start(self, event_callback):
        ext = AsyncIOScheduler()
        s = APSchedulerEventScheduler(
            event_callback=event_callback,
            scheduler=ext,
            auto_start=False,
        )
        assert not ext.running
        ext.start()
        task_id = await s.schedule_once("ev", "5s", {}, bot_id=1)
        assert isinstance(task_id, str)
        ext.shutdown(wait=False)
        await asyncio.sleep(0.1)
