gdelt 0.1.0

CLI for GDELT Project - optimized for agentic usage with local data caching
//! Task scheduling for the daemon.

#![allow(dead_code)]

use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};

/// A scheduled task
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScheduledTask {
    /// Task name
    pub name: String,
    /// Task schedule
    pub schedule: Schedule,
    /// Next run time
    pub next_run: DateTime<Utc>,
    /// Last run time
    pub last_run: Option<DateTime<Utc>>,
    /// Whether the task is enabled
    pub enabled: bool,
}

/// Task schedule configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Schedule {
    /// Run every N seconds
    Interval { seconds: u64 },
    /// Run at specific times (cron-like, simplified)
    Daily { hour: u8, minute: u8 },
}

impl ScheduledTask {
    /// Create a new interval-based task
    pub fn interval(name: &str, seconds: u64) -> Self {
        Self {
            name: name.to_string(),
            schedule: Schedule::Interval { seconds },
            next_run: Utc::now() + Duration::seconds(seconds as i64),
            last_run: None,
            enabled: true,
        }
    }

    /// Create a new daily task
    pub fn daily(name: &str, hour: u8, minute: u8) -> Self {
        let now = Utc::now();
        let today = now.date_naive();
        let run_time = today.and_hms_opt(hour as u32, minute as u32, 0)
            .unwrap_or_else(|| today.and_hms_opt(0, 0, 0).unwrap());

        let next_run = if run_time.and_utc() > now {
            run_time.and_utc()
        } else {
            (run_time + Duration::days(1)).and_utc()
        };

        Self {
            name: name.to_string(),
            schedule: Schedule::Daily { hour, minute },
            next_run,
            last_run: None,
            enabled: true,
        }
    }

    /// Check if it's time to run this task
    pub fn is_due(&self) -> bool {
        self.enabled && Utc::now() >= self.next_run
    }

    /// Mark the task as run and schedule the next run
    pub fn mark_run(&mut self) {
        self.last_run = Some(Utc::now());
        self.next_run = self.calculate_next_run();
    }

    fn calculate_next_run(&self) -> DateTime<Utc> {
        match &self.schedule {
            Schedule::Interval { seconds } => Utc::now() + Duration::seconds(*seconds as i64),
            Schedule::Daily { hour, minute } => {
                let now = Utc::now();
                let today = now.date_naive();
                let run_time = today.and_hms_opt(*hour as u32, *minute as u32, 0)
                    .unwrap_or_else(|| today.and_hms_opt(0, 0, 0).unwrap());

                if run_time.and_utc() > now {
                    run_time.and_utc()
                } else {
                    (run_time + Duration::days(1)).and_utc()
                }
            }
        }
    }
}

/// Task scheduler that manages multiple tasks
pub struct Scheduler {
    tasks: Vec<ScheduledTask>,
}

impl Scheduler {
    /// Create a new scheduler
    pub fn new() -> Self {
        Self { tasks: Vec::new() }
    }

    /// Add a task to the scheduler
    pub fn add_task(&mut self, task: ScheduledTask) {
        self.tasks.push(task);
    }

    /// Get all due tasks
    pub fn due_tasks(&self) -> Vec<&ScheduledTask> {
        self.tasks.iter().filter(|t| t.is_due()).collect()
    }

    /// Mark a task as run
    pub fn mark_task_run(&mut self, name: &str) {
        if let Some(task) = self.tasks.iter_mut().find(|t| t.name == name) {
            task.mark_run();
        }
    }

    /// Get time until next task is due
    pub fn time_until_next(&self) -> Option<std::time::Duration> {
        self.tasks
            .iter()
            .filter(|t| t.enabled)
            .map(|t| t.next_run)
            .min()
            .map(|next| {
                let now = Utc::now();
                if next > now {
                    (next - now).to_std().unwrap_or(std::time::Duration::ZERO)
                } else {
                    std::time::Duration::ZERO
                }
            })
    }
}

impl Default for Scheduler {
    fn default() -> Self {
        Self::new()
    }
}