use chrono::{DateTime, Utc};
use cron::Schedule as CronSchedule;
use serde::{Deserialize, Serialize};
use std::str::FromStr;

use crate::error::Error;

/// Defines the scheduling behavior for a job
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum JobSchedule {
    /// Run immediately without delay
    Immediate,

    /// Run once at a specific point in time
    OnceAt(DateTime<Utc>),

    /// Run repeatedly based on cron expression
    Cron {
        /// The cron expression string
        pattern: String,
    },
}

impl Default for JobSchedule {
    fn default() -> Self {
        Self::Immediate
    }
}

impl JobSchedule {
    /// Create a one-time scheduled job
    pub fn once_at(execution_time: DateTime<Utc>) -> Self {
        Self::OnceAt(execution_time)
    }

    /// Create a recurring job from a cron expression
    pub fn from_cron(pattern: &str) -> Result<Self, Error> {
        CronSchedule::from_str(pattern)
            .map_err(|e| Error::Config(format!("Invalid cron pattern '{}': {}", pattern, e)))?;

        Ok(Self::Cron {
            pattern: pattern.to_string(),
        })
    }

    /// Get the next execution time for this schedule
    pub fn next_run_time(&self) -> Option<DateTime<Utc>> {
        match self {
            Self::Immediate => Some(Utc::now()),
            Self::OnceAt(time) => Some(*time),
            Self::Cron { pattern } => {
                let cron_schedule = CronSchedule::from_str(pattern).ok()?;
                let now = Utc::now();
                cron_schedule.after(&now).next()
            }
        }
    }

    /// Get the next recurrence time after job completion
    pub fn next_recurrence(&self) -> Option<DateTime<Utc>> {
        match self {
            Self::Immediate => None,
            Self::OnceAt(_time) => None,
            Self::Cron { pattern } => {
                let cron_schedule = CronSchedule::from_str(pattern).ok()?;
                let now = Utc::now();
                cron_schedule.after(&now).next()
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::Duration;

    #[test]
    fn test_default_is_immediate() {
        // Arrange & Act
        let schedule = JobSchedule::default();

        // Assert
        assert_eq!(schedule, JobSchedule::Immediate);
    }

    #[test]
    fn test_immediate_next_run_time_is_now() {
        // Arrange
        let schedule = JobSchedule::Immediate;
        let before = Utc::now();

        // Act
        let next_run = schedule.next_run_time();

        // Assert
        let after = Utc::now();
        assert!(next_run.is_some());
        let next = next_run.unwrap();
        assert!(next >= before && next <= after);
    }

    #[test]
    fn test_once_at_returns_scheduled_time() {
        // Arrange
        let scheduled_time = Utc::now() + Duration::hours(2);
        let schedule = JobSchedule::once_at(scheduled_time);

        // Act
        let next_run = schedule.next_run_time();

        // Assert
        assert_eq!(next_run, Some(scheduled_time));
    }

    #[test]
    fn test_from_cron_with_valid_pattern() {
        // Arrange
        let pattern = "0 0 * * * *"; // Every hour

        // Act
        let result = JobSchedule::from_cron(pattern);

        // Assert
        assert!(result.is_ok());
        match result.unwrap() {
            JobSchedule::Cron { pattern: p } => assert_eq!(p, pattern),
            _ => panic!("Expected Cron variant"),
        }
    }

    #[test]
    fn test_from_cron_with_invalid_pattern() {
        // Arrange
        let pattern = "invalid cron";

        // Act
        let result = JobSchedule::from_cron(pattern);

        // Assert
        assert!(result.is_err());
    }

    #[test]
    fn test_cron_next_run_time_is_in_future() {
        // Arrange
        let pattern = "0 0 * * * *"; // Every hour
        let schedule = JobSchedule::from_cron(pattern).unwrap();
        let now = Utc::now();

        // Act
        let next_run = schedule.next_run_time();

        // Assert
        assert!(next_run.is_some());
        assert!(next_run.unwrap() > now);
    }

    #[test]
    fn test_equality() {
        // Arrange
        let time = Utc::now();
        let schedule1 = JobSchedule::once_at(time);
        let schedule2 = JobSchedule::once_at(time);

        // Act & Assert
        assert_eq!(schedule1, schedule2);
    }
}