from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any
from datetime import datetime, timedelta

if TYPE_CHECKING:
    from collections.abc import Callable, Awaitable

from ui_router.events import EventData
from ui_router.exceptions import EventSchedulingError
from ui_router.schema import EventSourceType
from ui_router.integrations._utils import parse_delay

try:
    from apscheduler.schedulers.asyncio import AsyncIOScheduler
    from apscheduler.triggers.cron import CronTrigger
    from apscheduler.jobstores.base import JobLookupError

    HAS_APSCHEDULER = True
except ImportError:
    HAS_APSCHEDULER = False

logger = logging.getLogger(__name__)


class APSchedulerEventScheduler:
    def __init__(
        self,
        event_callback: Callable[[EventData], Awaitable[None]],
        scheduler: Any | None = None,
        auto_start: bool = True,
    ) -> None:
        if not HAS_APSCHEDULER:
            msg = "apscheduler is not installed. Install it with: pip install aiogram-ui-router[apscheduler]"
            raise ImportError(msg)

        if scheduler is not None:
            self._scheduler: AsyncIOScheduler = scheduler
        else:
            self._scheduler = AsyncIOScheduler()

        self._event_callback = event_callback
        self._auto_start = auto_start
        self._started = False

    def _ensure_started(self) -> None:
        if self._auto_start and not self._started and not self._scheduler.running:
            self._scheduler.start()
            self._started = True

    async def _fire_event(
        self,
        event_name: str,
        event_data: dict[str, Any],
        bot_id: str | int,
        user_id: int | None,
        chat_id: int | None,
    ) -> None:
        event = EventData(
            event_name=event_name,
            source_type=EventSourceType.SCHEDULED,
            data=event_data,
            bot_id=bot_id,
            user_id=user_id,
            chat_id=chat_id,
        )
        try:
            await self._event_callback(event)
        except Exception:
            logger.exception("Error in scheduled event callback for '%s'", event_name)

    async def schedule_once(
        self,
        event_name: str,
        delay: str,
        event_data: dict[str, Any],
        bot_id: str | int,
        user_id: int | None = None,
        chat_id: int | None = None,
    ) -> str:
        self._ensure_started()
        delay_td = parse_delay(delay)
        run_date = datetime.now() + delay_td

        job = self._scheduler.add_job(
            self._fire_event,
            trigger="date",
            run_date=run_date,
            kwargs={
                "event_name": event_name,
                "event_data": event_data,
                "bot_id": bot_id,
                "user_id": user_id,
                "chat_id": chat_id,
            },
        )
        logger.debug("Scheduled once event '%s' at %s, job_id: %s", event_name, run_date, job.id)
        return job.id

    async def schedule_cron(
        self,
        event_name: str,
        cron_expr: str,
        event_data: dict[str, Any],
        bot_id: str | int,
        user_id: int | None = None,
        chat_id: int | None = None,
    ) -> str:
        self._ensure_started()
        try:
            trigger = CronTrigger.from_crontab(cron_expr)
        except ValueError as e:
            msg = f"Invalid cron expression: {cron_expr}"
            raise EventSchedulingError(msg) from e

        job = self._scheduler.add_job(
            self._fire_event,
            trigger=trigger,
            kwargs={
                "event_name": event_name,
                "event_data": event_data,
                "bot_id": bot_id,
                "user_id": user_id,
                "chat_id": chat_id,
            },
        )
        logger.debug("Scheduled cron event '%s' with '%s', job_id: %s", event_name, cron_expr, job.id)
        return job.id

    async def schedule_interval(
        self,
        event_name: str,
        interval_seconds: int,
        event_data: dict[str, Any],
        bot_id: str | int,
        user_id: int | None = None,
        chat_id: int | None = None,
    ) -> str:
        self._ensure_started()
        job = self._scheduler.add_job(
            self._fire_event,
            trigger="interval",
            seconds=interval_seconds,
            kwargs={
                "event_name": event_name,
                "event_data": event_data,
                "bot_id": bot_id,
                "user_id": user_id,
                "chat_id": chat_id,
            },
        )
        logger.debug("Scheduled interval event '%s' every %ds, job_id: %s", event_name, interval_seconds, job.id)
        return job.id

    async def cancel_scheduled(self, task_id: str) -> bool:
        try:
            self._scheduler.remove_job(task_id)
        except JobLookupError:
            logger.warning("Job %s not found for cancellation", task_id)
            return False
        else:
            logger.debug("Cancelled scheduled job %s", task_id)
            return True

    def shutdown(self, wait: bool = True) -> None:
        if self._scheduler.running:
            self._scheduler.shutdown(wait=wait)
            self._started = False
