"""
Tool Scheduler

Scheduled tool execution system with support for one-time, interval, and cron-based scheduling.
"""

import asyncio
import uuid
from datetime import datetime, timedelta
from enum import Enum
from typing import Any, Callable, Dict, Optional

from pydantic import BaseModel, Field


class ScheduleType(str, Enum):
    """Schedule type enumeration"""

    ONCE = "once"  # Single execution
    INTERVAL = "interval"  # Fixed interval
    CRON = "cron"  # Cron expression


class ScheduledTask(BaseModel):
    """Scheduled task model"""

    id: str = Field(default_factory=lambda: str(uuid.uuid4())[:8])
    name: str
    schedule_type: ScheduleType
    schedule_value: str  # datetime string / interval seconds / cron expression
    tool_name: str
    parameters: Dict[str, Any] = Field(default_factory=dict)
    enabled: bool = True
    last_run: Optional[datetime] = None
    next_run: Optional[datetime] = None
    run_count: int = 0
    created_at: datetime = Field(default_factory=datetime.utcnow)

    def to_dict(self) -> dict:
        return {
            "id": self.id,
            "name": self.name,
            "type": self.schedule_type.value,
            "schedule_value": self.schedule_value,
            "tool_name": self.tool_name,
            "enabled": self.enabled,
            "last_run": self.last_run.isoformat() if self.last_run else None,
            "next_run": self.next_run.isoformat() if self.next_run else None,
            "run_count": self.run_count,
        }


class ToolScheduler:
    """Tool scheduler for automated execution"""

    def __init__(self):
        self._tasks: Dict[str, ScheduledTask] = {}
        self._running: bool = False
        self._loop_task: Optional[asyncio.Task] = None
        self._executor: Optional[Callable] = None

    def set_executor(self, executor: Callable) -> None:
        """Set the tool executor function

        Args:
            executor: Async callable that takes (tool_name, command, parameters)
        """
        self._executor = executor

    async def schedule(
        self,
        name: str,
        schedule_type: ScheduleType,
        schedule_value: str,
        tool_name: str,
        parameters: Optional[Dict[str, Any]] = None,
    ) -> str:
        """Create a scheduled task

        Args:
            name: Task name
            schedule_type: Type of schedule
            schedule_value: Schedule value (datetime/interval/cron)
            tool_name: Tool to execute
            parameters: Tool parameters

        Returns:
            Task ID
        """
        task = ScheduledTask(
            name=name,
            schedule_type=schedule_type,
            schedule_value=schedule_value,
            tool_name=tool_name,
            parameters=parameters or {},
        )

        task.next_run = self._calculate_next_run(task)
        self._tasks[task.id] = task

        # Start scheduler loop if not running
        if not self._running:
            await self.start()

        return task.id

    async def schedule_once(
        self,
        name: str,
        run_at: str,  # ISO datetime string
        tool_name: str,
        parameters: Optional[Dict[str, Any]] = None,
    ) -> str:
        """Schedule a one-time task

        Args:
            name: Task name
            run_at: ISO datetime string
            tool_name: Tool to execute
            parameters: Tool parameters

        Returns:
            Task ID
        """
        return await self.schedule(
            name=name,
            schedule_type=ScheduleType.ONCE,
            schedule_value=run_at,
            tool_name=tool_name,
            parameters=parameters,
        )

    async def schedule_interval(
        self,
        name: str,
        interval_seconds: int,
        tool_name: str,
        parameters: Optional[Dict[str, Any]] = None,
    ) -> str:
        """Schedule an interval-based recurring task

        Args:
            name: Task name
            interval_seconds: Interval in seconds
            tool_name: Tool to execute
            parameters: Tool parameters

        Returns:
            Task ID
        """
        return await self.schedule(
            name=name,
            schedule_type=ScheduleType.INTERVAL,
            schedule_value=str(interval_seconds),
            tool_name=tool_name,
            parameters=parameters,
        )

    async def schedule_cron(
        self,
        name: str,
        cron_expression: str,
        tool_name: str,
        parameters: Optional[Dict[str, Any]] = None,
    ) -> str:
        """Schedule a cron-based recurring task

        Args:
            name: Task name
            cron_expression: Cron expression (5 fields: min hour day month weekday)
            tool_name: Tool to execute
            parameters: Tool parameters

        Returns:
            Task ID
        """
        return await self.schedule(
            name=name,
            schedule_type=ScheduleType.CRON,
            schedule_value=cron_expression,
            tool_name=tool_name,
            parameters=parameters,
        )

    def _calculate_next_run(self, task: ScheduledTask) -> Optional[datetime]:
        """Calculate the next run time for a task

        Args:
            task: The scheduled task

        Returns:
            Next run datetime or None if schedule type is invalid
        """
        now = datetime.utcnow()

        if task.schedule_type == ScheduleType.ONCE:
            try:
                return datetime.fromisoformat(task.schedule_value)
            except ValueError:
                return None

        elif task.schedule_type == ScheduleType.INTERVAL:
            try:
                seconds = int(task.schedule_value)
                return now + timedelta(seconds=seconds)
            except ValueError:
                return None

        elif task.schedule_type == ScheduleType.CRON:
            return self._parse_cron_next(task.schedule_value, now)

        return None

    def _parse_cron_next(self, cron_expr: str, base_time: datetime) -> datetime:
        """Parse cron expression and calculate next run

        Args:
            cron_expr: Cron expression (min hour day month weekday)
            base_time: Reference datetime

        Returns:
            Next matching datetime
        """
        try:
            parts = cron_expr.split()
            if len(parts) != 5:
                return base_time + timedelta(hours=1)

            minute, hour, day, month, weekday = parts

            # Simple next-run calculation
            # For production, use croniter library
            next_time = base_time.replace(second=0, microsecond=0) + timedelta(minutes=1)

            # Basic validation and advancement
            max_iterations = 366 * 24 * 60  # 1 year of minutes max
            for _ in range(max_iterations):
                if self._cron_matches(next_time, minute, hour, day, month, weekday):
                    return next_time
                next_time += timedelta(minutes=1)

            return next_time

        except Exception:
            return base_time + timedelta(hours=1)

    def _cron_matches(
        self,
        dt: datetime,
        minute: str,
        hour: str,
        day: str,
        month: str,
        weekday: str,
    ) -> bool:
        """Check if datetime matches cron fields

        Args:
            dt: Datetime to check
            minute: Minute field
            hour: Hour field
            day: Day of month field
            month: Month field
            weekday: Day of week field

        Returns:
            True if matches
        """
        return (
            self._cron_field_matches(dt.minute, minute)
            and self._cron_field_matches(dt.hour, hour)
            and self._cron_field_matches(dt.day, day)
            and self._cron_field_matches(dt.month, month)
            and self._cron_field_matches(dt.weekday(), weekday)
        )

    def _cron_field_matches(self, value: int, field: str) -> bool:
        """Check if a value matches a cron field

        Args:
            value: The actual value
            field: Cron field (number, *, */n, n,m, n-m)

        Returns:
            True if matches
        """
        if field == "*":
            return True

        if field.startswith("*/"):
            try:
                step = int(field[2:])
                return value % step == 0
            except ValueError:
                return False

        if "," in field:
            return str(value) in field.split(",")

        if "-" in field:
            try:
                start, end = field.split("-")
                return start <= str(value) <= end
            except ValueError:
                return False

        try:
            return int(field) == value
        except ValueError:
            return False

    async def start(self) -> None:
        """Start the scheduler loop"""
        if self._running:
            return
        self._running = True
        self._loop_task = asyncio.create_task(self._run_loop())

    async def stop(self) -> None:
        """Stop the scheduler loop"""
        self._running = False
        if self._loop_task:
            self._loop_task.cancel()
            try:
                await self._loop_task
            except asyncio.CancelledError:
                pass
            self._loop_task = None

    async def _run_loop(self) -> None:
        """Main scheduler loop"""
        while self._running:
            now = datetime.utcnow()

            for task in list(self._tasks.values()):
                if not task.enabled:
                    continue

                if task.next_run and task.next_run <= now:
                    await self._execute_task(task)

            await asyncio.sleep(1)  # Check every second

    async def _execute_task(self, task: ScheduledTask) -> None:
        """Execute a scheduled task

        Args:
            task: The task to execute
        """
        if self._executor is None:
            return

        try:
            await self._executor(
                tool_name=task.tool_name,
                command=task.parameters.get("command", ""),
                parameters=task.parameters,
            )
        except Exception:
            pass  # Log error in production

        # Update task state
        task.last_run = datetime.utcnow()
        task.run_count += 1

        # Calculate next run or disable one-time tasks
        if task.schedule_type != ScheduleType.ONCE:
            task.next_run = self._calculate_next_run(task)
        else:
            task.enabled = False

    async def cancel(self, task_id: str) -> bool:
        """Cancel a scheduled task

        Args:
            task_id: Task ID

        Returns:
            True if cancelled
        """
        if task_id in self._tasks:
            del self._tasks[task_id]
            return True
        return False

    async def enable(self, task_id: str) -> bool:
        """Enable a scheduled task

        Args:
            task_id: Task ID

        Returns:
            True if enabled
        """
        task = self._tasks.get(task_id)
        if task:
            task.enabled = True
            task.next_run = self._calculate_next_run(task)
            return True
        return False

    async def disable(self, task_id: str) -> bool:
        """Disable a scheduled task

        Args:
            task_id: Task ID

        Returns:
            True if disabled
        """
        task = self._tasks.get(task_id)
        if task:
            task.enabled = False
            return True
        return False

    async def list_tasks(self) -> list:
        """List all scheduled tasks

        Returns:
            List of task info dicts
        """
        return [task.to_dict() for task in self._tasks.values()]

    async def get_task(self, task_id: str) -> Optional[dict]:
        """Get a specific task

        Args:
            task_id: Task ID

        Returns:
            Task dict or None
        """
        task = self._tasks.get(task_id)
        return task.to_dict() if task else None


# Global scheduler instance
_scheduler: Optional[ToolScheduler] = None


def get_scheduler() -> ToolScheduler:
    """Get the global tool scheduler instance"""
    global _scheduler
    if _scheduler is None:
        _scheduler = ToolScheduler()
    return _scheduler
