"""
Pipeline scheduler for Aperion Legendary agent system.

.. note::
    Consider using :mod:`stack.aperion.pulse` for new scheduling needs.
    The Pulse provides a unified scheduler with heartbeat and watchdog features.

    Migration example::

        # Old (pipeline scheduler)
        from stack.aperion.pipelines.scheduler import PipelineScheduler
        scheduler = PipelineScheduler()
        scheduler.schedule_recurring(pipeline, interval_seconds=60)

        # New (recommended for generic tasks)
        from stack.aperion.pulse import Pulse
        pulse = Pulse()
        pulse.schedule_interval(my_handler, interval_seconds=60)

    PipelineScheduler remains useful for Pipeline-specific scheduling.
"""

import asyncio
import logging
import time
import uuid
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Optional

from .executor import PipelineExecutor
from .pipeline import Pipeline

logger = logging.getLogger(__name__)


class ScheduleType(Enum):
    """Types of pipeline scheduling."""

    IMMEDIATE = "immediate"
    DELAYED = "delayed"
    RECURRING = "recurring"
    CRON = "cron"


@dataclass
class ScheduledTask:
    """A scheduled pipeline execution task."""

    id: str = field(default_factory=lambda: str(uuid.uuid4()))
    pipeline: Pipeline = None
    schedule_type: ScheduleType = ScheduleType.IMMEDIATE
    scheduled_time: Optional[float] = None
    interval_seconds: Optional[int] = None
    cron_expression: Optional[str] = None
    max_executions: Optional[int] = None
    execution_count: int = 0
    enabled: bool = True
    created_at: float = field(default_factory=time.time)
    last_execution: Optional[float] = None
    next_execution: Optional[float] = None

    def should_execute(self, current_time: float) -> bool:
        """Check if task should be executed at current time."""
        if not self.enabled:
            return False

        if self.max_executions and self.execution_count >= self.max_executions:
            return False

        if self.next_execution and current_time >= self.next_execution:
            return True

        return False

    def calculate_next_execution(self, current_time: float) -> None:
        """Calculate the next execution time."""
        if self.schedule_type == ScheduleType.IMMEDIATE:
            self.next_execution = current_time
        elif self.schedule_type == ScheduleType.DELAYED:
            self.next_execution = self.scheduled_time
        elif self.schedule_type == ScheduleType.RECURRING:
            if self.interval_seconds:
                if self.last_execution:
                    self.next_execution = self.last_execution + self.interval_seconds
                else:
                    self.next_execution = current_time
        # CRON scheduling can be implemented here as needed

    def mark_executed(self, execution_time: float) -> None:
        """Mark task as executed."""
        self.execution_count += 1
        self.last_execution = execution_time

        # Calculate next execution for recurring tasks
        if self.schedule_type == ScheduleType.RECURRING:
            self.calculate_next_execution(execution_time)


class PipelineScheduler:
    """
    Pipeline scheduler for managing and executing scheduled pipeline tasks.

    Supports immediate, delayed, and recurring pipeline executions with
    configurable scheduling parameters and execution limits.
    """

    def __init__(self, executor: Optional[PipelineExecutor] = None):
        """
        Initialize the pipeline scheduler.

        Args:
            executor: Pipeline executor instance (creates default if None)
        """
        self.executor = executor or PipelineExecutor()
        self.scheduled_tasks: dict[str, ScheduledTask] = {}
        self.running = False
        self._scheduler_task: Optional[asyncio.Task] = None
        self._check_interval = 1.0  # Check for tasks every second

    async def start(self) -> None:
        """Start the scheduler."""
        if self.running:
            return

        logger.info("Starting pipeline scheduler")
        self.running = True
        self._scheduler_task = asyncio.create_task(self._scheduler_loop())

    async def stop(self) -> None:
        """Stop the scheduler."""
        if not self.running:
            return

        logger.info("Stopping pipeline scheduler")
        self.running = False

        if self._scheduler_task:
            self._scheduler_task.cancel()
            try:
                await self._scheduler_task
            except asyncio.CancelledError:
                pass
            self._scheduler_task = None

    def schedule_pipeline(
        self,
        pipeline: Pipeline,
        schedule_type: ScheduleType = ScheduleType.IMMEDIATE,
        delay_seconds: Optional[int] = None,
        interval_seconds: Optional[int] = None,
        cron_expression: Optional[str] = None,
        max_executions: Optional[int] = None,
    ) -> str:
        """
        Schedule a pipeline for execution.

        Args:
            pipeline: Pipeline to schedule
            schedule_type: Type of scheduling
            delay_seconds: Delay before execution (for DELAYED type)
            interval_seconds: Interval between executions (for RECURRING type)
            cron_expression: Cron expression (for CRON type)
            max_executions: Maximum number of executions

        Returns:
            Task ID
        """
        current_time = time.time()

        task = ScheduledTask(
            pipeline=pipeline,
            schedule_type=schedule_type,
            interval_seconds=interval_seconds,
            cron_expression=cron_expression,
            max_executions=max_executions,
        )

        # Set scheduled time for delayed tasks
        if schedule_type == ScheduleType.DELAYED and delay_seconds:
            task.scheduled_time = current_time + delay_seconds

        # Calculate initial next execution time
        task.calculate_next_execution(current_time)

        self.scheduled_tasks[task.id] = task

        logger.info(
            f"Scheduled pipeline {pipeline.name} ({pipeline.id}) "
            f"as task {task.id} with type {schedule_type.value}"
        )

        return task.id

    def schedule_immediate(self, pipeline: Pipeline) -> str:
        """Schedule a pipeline for immediate execution."""
        return self.schedule_pipeline(pipeline, ScheduleType.IMMEDIATE)

    def schedule_delayed(self, pipeline: Pipeline, delay_seconds: int) -> str:
        """Schedule a pipeline for delayed execution."""
        return self.schedule_pipeline(
            pipeline, ScheduleType.DELAYED, delay_seconds=delay_seconds
        )

    def schedule_recurring(
        self,
        pipeline: Pipeline,
        interval_seconds: int,
        max_executions: Optional[int] = None,
    ) -> str:
        """Schedule a pipeline for recurring execution."""
        return self.schedule_pipeline(
            pipeline,
            ScheduleType.RECURRING,
            interval_seconds=interval_seconds,
            max_executions=max_executions,
        )

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

        Args:
            task_id: ID of task to cancel

        Returns:
            True if task was cancelled, False if not found
        """
        task = self.scheduled_tasks.get(task_id)
        if task:
            task.enabled = False
            logger.info(f"Cancelled scheduled task {task_id}")
            return True
        return False

    def remove_task(self, task_id: str) -> bool:
        """
        Remove a scheduled task.

        Args:
            task_id: ID of task to remove

        Returns:
            True if task was removed, False if not found
        """
        if task_id in self.scheduled_tasks:
            del self.scheduled_tasks[task_id]
            logger.info(f"Removed scheduled task {task_id}")
            return True
        return False

    def get_task(self, task_id: str) -> Optional[ScheduledTask]:
        """Get a scheduled task by ID."""
        return self.scheduled_tasks.get(task_id)

    def list_tasks(self) -> list[ScheduledTask]:
        """List all scheduled tasks."""
        return list(self.scheduled_tasks.values())

    def get_task_status(self, task_id: str) -> Optional[dict[str, Any]]:
        """Get status information for a scheduled task."""
        task = self.scheduled_tasks.get(task_id)
        if not task:
            return None

        return {
            "id": task.id,
            "pipeline_id": task.pipeline.id if task.pipeline else None,
            "pipeline_name": task.pipeline.name if task.pipeline else None,
            "schedule_type": task.schedule_type.value,
            "enabled": task.enabled,
            "execution_count": task.execution_count,
            "max_executions": task.max_executions,
            "next_execution": task.next_execution,
            "last_execution": task.last_execution,
            "created_at": task.created_at,
        }

    async def _scheduler_loop(self) -> None:
        """Main scheduler loop."""
        logger.info("Scheduler loop started")

        while self.running:
            try:
                current_time = time.time()

                # Check for tasks ready to execute
                ready_tasks = [
                    task
                    for task in self.scheduled_tasks.values()
                    if task.should_execute(current_time)
                ]

                # Execute ready tasks
                for task in ready_tasks:
                    try:
                        await self._execute_task(task, current_time)
                    except Exception as e:
                        logger.error(f"Failed to execute task {task.id}: {e}")

                # Clean up completed one-time tasks
                self._cleanup_completed_tasks()

                # Wait before next check
                await asyncio.sleep(self._check_interval)

            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Error in scheduler loop: {e}")
                await asyncio.sleep(self._check_interval)

        logger.info("Scheduler loop stopped")

    async def _execute_task(self, task: ScheduledTask, execution_time: float) -> None:
        """Execute a scheduled task."""
        if not task.pipeline:
            logger.error(f"Task {task.id} has no pipeline to execute")
            return

        logger.info(
            f"Executing scheduled task {task.id} for pipeline {task.pipeline.name}"
        )

        try:
            # Execute pipeline asynchronously
            await self.executor.execute_pipeline_async(task.pipeline)

            # Mark task as executed
            task.mark_executed(execution_time)

            logger.info(f"Task {task.id} executed successfully")

        except Exception as e:
            logger.error(f"Task {task.id} execution failed: {e}")
            # Don't mark as executed if it failed

    def _cleanup_completed_tasks(self) -> None:
        """Remove completed one-time tasks."""
        completed_task_ids = []

        for task_id, task in self.scheduled_tasks.items():
            # Remove one-time tasks that have been executed
            if (
                task.schedule_type in [ScheduleType.IMMEDIATE, ScheduleType.DELAYED]
                and task.execution_count > 0
            ):
                completed_task_ids.append(task_id)
            # Remove recurring tasks that have reached max executions
            elif task.max_executions and task.execution_count >= task.max_executions:
                completed_task_ids.append(task_id)

        for task_id in completed_task_ids:
            del self.scheduled_tasks[task_id]
            logger.debug(f"Cleaned up completed task {task_id}")

    async def __aenter__(self):
        """Async context manager entry."""
        await self.start()
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """Async context manager exit."""
        await self.stop()
