"""
Task queue system for rateloader.

Implements Phase 6 of the rateloader project: Sequential task queue & scheduler.
Provides FIFO queue with single worker thread for strict sequential execution.
"""

import queue
import threading
import time
from typing import Callable, Dict, Any, Optional
from enum import Enum

from core.logger import get_rateloader_logger, get_queue_logger


class TaskStatus(Enum):
    """Task execution status enumeration."""
    PENDING = "pending"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"


class Task:
    """
    Base task class for the task queue system.
    
    All tasks should inherit from this class and implement the execute method.
    """
    
    def __init__(self, name: str, description: str = ""):
        """
        Initialize task.
        
        Args:
            name: Task name for identification
            description: Optional task description
        """
        self.name = name
        self.description = description
        self.status = TaskStatus.PENDING
        self.created_at = time.time()
        self.started_at: Optional[float] = None
        self.completed_at: Optional[float] = None
        self.error_message: Optional[str] = None
        self.result: Optional[Any] = None
    
    def execute(self) -> bool:
        """
        Execute the task.
        
        This method should be overridden by subclasses.
        
        Returns:
            True if task executed successfully, False otherwise
        """
        raise NotImplementedError("Subclasses must implement the execute method")
    
    def mark_started(self) -> None:
        """Mark task as started."""
        self.status = TaskStatus.RUNNING
        self.started_at = time.time()
    
    def mark_completed(self, success: bool, error_message: Optional[str] = None, result: Optional[Any] = None) -> None:
        """
        Mark task as completed.
        
        Args:
            success: Whether task completed successfully
            error_message: Error message if task failed
            result: Task result if successful
        """
        self.status = TaskStatus.COMPLETED if success else TaskStatus.FAILED
        self.completed_at = time.time()
        self.error_message = error_message
        self.result = result
    
    def to_dict(self) -> Dict[str, Any]:
        """
        Convert task to dictionary representation.
        
        Returns:
            Dictionary with task information
        """
        return {
            "name": self.name,
            "description": self.description,
            "status": self.status.value,
            "created_at": self.created_at,
            "started_at": self.started_at,
            "completed_at": self.completed_at,
            "error_message": self.error_message,
            "duration": (self.completed_at - self.started_at) if self.started_at and self.completed_at else None
        }


class TaskQueue:
    """
    Sequential task queue with single worker thread.
    
    Provides FIFO queue with guaranteed sequential execution.
    All exceptions are caught and logged without crashing the worker.
    """
    
    def __init__(self):
        """Initialize task queue."""
        self.logger = get_queue_logger()
        self._queue = queue.Queue()
        self._worker_thread: Optional[threading.Thread] = None
        self._stop_event = threading.Event()
        self._current_task: Optional[Task] = None
        
        # Statistics
        self._stats = {
            "total_enqueued": 0,
            "total_completed": 0,
            "total_failed": 0,
            "queue_length": 0
        }
        
        self.logger.info("Task queue initialized")
    
    def enqueue(self, task: Task) -> bool:
        """
        Enqueue a task for execution.
        
        Args:
            task: Task to enqueue
            
        Returns:
            True if task enqueued successfully, False otherwise
        """
        try:
            self._queue.put(task)
            self._stats["total_enqueued"] += 1
            self._stats["queue_length"] = self._queue.qsize()
            self.logger.info(f"Task enqueued: {task.name}")
            return True
        except Exception as e:
            self.logger.error(f"Failed to enqueue task {task.name}: {str(e)}")
            return False
    
    def start(self) -> bool:
        """
        Start the worker thread.
        
        Returns:
            True if worker started successfully, False otherwise
        """
        if self._worker_thread and self._worker_thread.is_alive():
            self.logger.warning("Task queue worker is already running")
            return True
        
        try:
            self._stop_event.clear()
            self._worker_thread = threading.Thread(target=self._worker_loop, daemon=True)
            self._worker_thread.start()
            self.logger.info("Task queue worker started")
            return True
        except Exception as e:
            self.logger.error(f"Failed to start task queue worker: {str(e)}")
            return False
    
    def stop(self) -> bool:
        """
        Stop the worker thread.
        
        Returns:
            True if worker stopped successfully, False otherwise
        """
        try:
            self._stop_event.set()
            
            # Wait for worker thread to finish
            if self._worker_thread and self._worker_thread.is_alive():
                self._worker_thread.join(timeout=5)
            
            self.logger.info("Task queue worker stopped")
            return True
        except Exception as e:
            self.logger.error(f"Error stopping task queue worker: {str(e)}")
            return False
    
    def _worker_loop(self) -> None:
        """
        Main worker loop that processes tasks sequentially.
        
        Runs in a separate thread and processes tasks from the queue.
        All exceptions are caught and logged without crashing the worker.
        """
        self.logger.info("Task queue worker loop started")
        
        while not self._stop_event.is_set():
            try:
                # Get task from queue with timeout
                try:
                    task = self._queue.get(timeout=1.0)
                except queue.Empty:
                    continue
                
                # Execute task
                self._execute_task(task)
                
                # Mark task as done
                self._queue.task_done()
                
            except Exception as e:
                self.logger.error(f"Unexpected error in worker loop: {str(e)}")
                # Continue running even if there's an error
        
        self.logger.info("Task queue worker loop stopped")
    
    def _execute_task(self, task: Task) -> None:
        """
        Execute a single task with error handling.
        
        Args:
            task: Task to execute
        """
        self._current_task = task
        self._stats["queue_length"] = self._queue.qsize()
        
        try:
            self.logger.info(f"Executing task: {task.name}")
            task.mark_started()
            
            # Execute the task
            success = task.execute()
            
            if success:
                self._stats["total_completed"] += 1
                task.mark_completed(success=True, result=task.result)
                self.logger.info(f"Task completed successfully: {task.name}")
            else:
                self._stats["total_failed"] += 1
                task.mark_completed(success=False, error_message=task.error_message)
                self.logger.error(f"Task failed: {task.name} - {task.error_message}")
                
        except Exception as e:
            self._stats["total_failed"] += 1
            error_msg = f"Exception during task execution: {str(e)}"
            task.mark_completed(success=False, error_message=error_msg)
            self.logger.error(f"Task {task.name} failed with exception: {str(e)}", exc_info=True)
        
        finally:
            self._current_task = None
    
    def get_status(self) -> Dict[str, Any]:
        """
        Get current queue status.
        
        Returns:
            Dictionary with queue status information
        """
        current_task_info = None
        if self._current_task:
            current_task_info = self._current_task.to_dict()
        
        return {
            "worker_running": self._worker_thread is not None and self._worker_thread.is_alive(),
            "queue_length": self._queue.qsize(),
            "current_task": current_task_info,
            "statistics": self._stats.copy()
        }
    
    def is_running(self) -> bool:
        """
        Check if the worker thread is running.
        
        Returns:
            True if worker is running, False otherwise
        """
        return self._worker_thread is not None and self._worker_thread.is_alive()
    
    def get_queue_length(self) -> int:
        """
        Get current queue length.
        
        Returns:
            Number of tasks in the queue
        """
        return self._queue.qsize()


# Global task queue instance
_task_queue = None


def get_task_queue() -> TaskQueue:
    """
    Get the global task queue instance.
    
    Returns:
        Global TaskQueue instance
    """
    global _task_queue
    if _task_queue is None:
        _task_queue = TaskQueue()
    return _task_queue


def start_task_queue() -> bool:
    """
    Start the global task queue.
    
    Returns:
        True if task queue started successfully, False otherwise
    """
    task_queue = get_task_queue()
    return task_queue.start()


def stop_task_queue() -> bool:
    """
    Stop the global task queue.
    
    Returns:
        True if task queue stopped successfully, False otherwise
    """
    task_queue = get_task_queue()
    return task_queue.stop()


def enqueue_task(task: Task) -> bool:
    """
    Enqueue a task to the global task queue.
    
    Args:
        task: Task to enqueue
        
    Returns:
        True if task enqueued successfully, False otherwise
    """
    task_queue = get_task_queue()
    return task_queue.enqueue(task)


def get_queue_status() -> Dict[str, Any]:
    """
    Get status of the global task queue.
    
    Returns:
        Dictionary with queue status information
    """
    task_queue = get_task_queue()
    return task_queue.get_status()