"""
Scheduler system for rateloader.

Implements Phase 6 of the rateloader project: Sequential task queue & scheduler.
Wakes every 30 seconds to check scheduled times and enqueues tasks at specified hours.
"""

import time
import threading
from datetime import datetime
from typing import List, Optional

from core.config import get_config
from core.logger import get_scheduler_logger
from core.task_queue import get_task_queue, enqueue_task


class Scheduler:
    """
    Task scheduler for rateloader.
    
    Wakes every 30 seconds to check scheduled times and enqueues tasks
    at the specified hours (default: 8,12,15,18,20).
    Runs in a separate thread and handles errors gracefully.
    """
    
    def __init__(self):
        """Initialize scheduler."""
        self.config = get_config()
        self.logger = get_scheduler_logger()
        self.task_queue = get_task_queue()

        self._scheduler_thread: Optional[threading.Thread] = None
        self._stop_event = threading.Event()
        self._is_running = False

        # Monitoring attributes
        self.last_run_time: Optional[datetime] = None
        self.last_run_status: Optional[str] = None
        self.last_run_tasks: Optional[dict] = None

        # Get schedule hours from config
        self.schedule_hours = self.config.get_schedule_hours()

        self.logger.info(f"Scheduler initialized with schedule hours: {self.schedule_hours}")
    
    def start(self) -> bool:
        """
        Start the scheduler in a separate thread.
        
        Returns:
            True if scheduler started successfully, False otherwise
        """
        if self._is_running:
            self.logger.warning("Scheduler is already running")
            return True
        
        try:
            self._stop_event.clear()
            self._scheduler_thread = threading.Thread(target=self._scheduler_loop, daemon=True)
            self._scheduler_thread.start()
            self._is_running = True
            self.logger.info("Scheduler started")
            return True
        except Exception as e:
            self.logger.error(f"Failed to start scheduler: {str(e)}")
            return False
    
    def stop(self) -> bool:
        """
        Stop the scheduler.
        
        Returns:
            True if scheduler stopped successfully, False otherwise
        """
        if not self._is_running:
            self.logger.warning("Scheduler is not running")
            return True
        
        try:
            self._stop_event.set()
            
            # Wait for scheduler thread to finish
            if self._scheduler_thread and self._scheduler_thread.is_alive():
                self._scheduler_thread.join(timeout=5)
            
            self._is_running = False
            self.logger.info("Scheduler stopped")
            return True
        except Exception as e:
            self.logger.error(f"Error stopping scheduler: {str(e)}")
            return False
    
    def _scheduler_loop(self) -> None:
        """
        Main scheduler loop.

        Wakes every 30 seconds to check if current time matches scheduled hours.
        At each scheduled hour:00, enqueues ExportMailer then PriceExport tasks.
        Reloads configuration on each cycle to pick up changes.
        """
        self.logger.info("Scheduler loop started")

        while not self._stop_event.is_set():
            try:
                # Reload configuration on each cycle to pick up changes
                from core.config import reload_config
                reload_config()
                self.config = get_config()

                # Reload schedule hours from updated config
                new_schedule_hours = self.config.get_schedule_hours()
                if new_schedule_hours != self.schedule_hours:
                    self.logger.info(f"Schedule hours updated from {self.schedule_hours} to {new_schedule_hours}")
                    self.schedule_hours = new_schedule_hours

                # Get current time
                now = datetime.now()
                current_hour = now.hour
                current_minute = now.minute

                # Check if we're at a scheduled hour exactly at :00 minutes
                if current_minute == 0 and current_hour in self.schedule_hours:
                    self.logger.info(f"Scheduled time reached: {current_hour:02d}:00")
                    self._enqueue_scheduled_tasks()

                # Sleep for 30 seconds
                self._stop_event.wait(30.0)

            except Exception as e:
                self.logger.error(f"Error in scheduler loop: {str(e)}")
                # Continue running even if there's an error
                self._stop_event.wait(30.0)

        self.logger.info("Scheduler loop stopped")
    
    def _enqueue_scheduled_tasks(self) -> None:
        """
        Enqueue the scheduled tasks in the correct order.

        At each scheduled time, enqueues:
        1. ExportMailer (CSV → Email)
        2. PriceExport (JSON → API)

        Updates monitoring information for last run tracking.
        """
        try:
            # Record run start time
            run_start_time = datetime.now()
            task_results = {"export_mailer": None, "price_export": None}

            # Import task classes here to avoid circular imports
            from core.tasks import ExportMailerTask, PriceExportTask

            # Enqueue ExportMailer task first
            export_mailer_task = ExportMailerTask()
            if enqueue_task(export_mailer_task):
                self.logger.info("ExportMailer task enqueued successfully")
                task_results["export_mailer"] = "enqueued"
            else:
                self.logger.error("Failed to enqueue ExportMailer task")
                task_results["export_mailer"] = "failed_to_enqueue"

            # Enqueue PriceExport task second (if enabled)
            enable_price_export = self.config.get_bool("enable_price_export_scheduling", fallback=True)
            if enable_price_export:
                price_export_task = PriceExportTask()
                if enqueue_task(price_export_task):
                    self.logger.info("PriceExport task enqueued successfully")
                    task_results["price_export"] = "enqueued"
                else:
                    self.logger.error("Failed to enqueue PriceExport task")
                    task_results["price_export"] = "failed_to_enqueue"
            else:
                self.logger.info("PriceExport scheduling disabled by configuration")
                task_results["price_export"] = "disabled"

            # Update monitoring information
            self.last_run_time = run_start_time
            self.last_run_status = "tasks_enqueued"
            self.last_run_tasks = task_results

            self.logger.info(f"Scheduled tasks enqueued at {run_start_time}")

        except Exception as e:
            self.logger.error(f"Error enqueuing scheduled tasks: {str(e)}")
            # Update monitoring even on failure
            self.last_run_time = datetime.now()
            self.last_run_status = f"error: {str(e)}"
            self.last_run_tasks = {"error": str(e)}
    
    def update_schedule_hours(self, hours: List[int]) -> None:
        """
        Update the schedule hours.

        Args:
            hours: List of hours (0-23) when tasks should run
        """
        try:
            # Validate hours
            valid_hours = []
            for hour in hours:
                if 0 <= hour <= 23:
                    valid_hours.append(hour)
                else:
                    self.logger.warning(f"Invalid hour {hour}, must be between 0 and 23")

            if valid_hours:
                old_hours = self.schedule_hours
                self.schedule_hours = valid_hours
                # Update config
                hours_str = ",".join(str(h) for h in valid_hours)
                self.config.set("schedule_hours", hours_str)
                self.logger.info(f"Schedule hours updated from {old_hours} to: {valid_hours}")

                # Log next scheduled time after update
                next_time = self.get_next_scheduled_time()
                if next_time:
                    self.logger.info(f"Next scheduled run: {next_time}")
            else:
                self.logger.error("No valid hours provided, schedule not updated")

        except Exception as e:
            self.logger.error(f"Error updating schedule hours: {str(e)}")
    
    def get_next_scheduled_time(self) -> Optional[datetime]:
        """
        Get the next scheduled time.
        
        Returns:
            Next scheduled datetime, or None if no schedule is configured
        """
        try:
            if not self.schedule_hours:
                return None
            
            now = datetime.now()
            current_hour = now.hour
            
            # Find next scheduled hour today
            next_hour = None
            for hour in sorted(self.schedule_hours):
                if hour > current_hour:
                    next_hour = hour
                    break
            
            # If no scheduled hour left today, find first one tomorrow
            if next_hour is None:
                next_hour = min(self.schedule_hours)
                next_time = now.replace(hour=next_hour, minute=0, second=0, microsecond=0)
                next_time = next_time.replace(day=next_time.day + 1)
            else:
                next_time = now.replace(hour=next_hour, minute=0, second=0, microsecond=0)
            
            return next_time
            
        except Exception as e:
            self.logger.error(f"Error getting next scheduled time: {str(e)}")
            return None
    
    def get_status(self) -> dict:
        """
        Get scheduler status.

        Returns:
            Dictionary with scheduler status information including monitoring data
        """
        next_time = self.get_next_scheduled_time()

        return {
            "running": self._is_running,
            "schedule_hours": self.schedule_hours,
            "next_scheduled_time": next_time.isoformat() if next_time else None,
            "thread_alive": self._scheduler_thread is not None and self._scheduler_thread.is_alive(),
            "last_run_time": self.last_run_time.isoformat() if self.last_run_time else None,
            "last_run_status": self.last_run_status,
            "last_run_tasks": self.last_run_tasks,
            "current_config": {
                "schedule_hours": self.schedule_hours,
                "auto_update_sql": self.config.get_bool("auto_update_sql", fallback=True),
                "compress_attachments": self.config.get_bool("compress_attachments", fallback=False)
            }
        }
    
    def is_running(self) -> bool:
        """
        Check if the scheduler is running.
        
        Returns:
            True if scheduler is running, False otherwise
        """
        return self._is_running


# Global scheduler instance
_scheduler = None


def get_scheduler() -> Scheduler:
    """
    Get the global scheduler instance.
    
    Returns:
        Global Scheduler instance
    """
    global _scheduler
    if _scheduler is None:
        _scheduler = Scheduler()
    return _scheduler


def start_scheduler() -> bool:
    """
    Start the global scheduler.
    
    Returns:
        True if scheduler started successfully, False otherwise
    """
    scheduler = get_scheduler()
    return scheduler.start()


def stop_scheduler() -> bool:
    """
    Stop the global scheduler.
    
    Returns:
        True if scheduler stopped successfully, False otherwise
    """
    scheduler = get_scheduler()
    return scheduler.stop()


def get_scheduler_status() -> dict:
    """
    Get status of the global scheduler.
    
    Returns:
        Dictionary with scheduler status information
    """
    scheduler = get_scheduler()
    return scheduler.get_status()