//! CronJob specification builder.

use crate::job::JobSpec;

/// Concurrency policy for CronJobs.
#[derive(Debug, Clone, PartialEq)]
pub enum ConcurrencyPolicy {
    /// Allow concurrent job runs.
    Allow,
    /// Forbid concurrent runs, skip new run if previous is still running.
    Forbid,
    /// Replace the currently running job with a new one.
    Replace,
}

impl ConcurrencyPolicy {
    /// Convert to string representation.
    pub fn as_str(&self) -> &str {
        match self {
            ConcurrencyPolicy::Allow => "Allow",
            ConcurrencyPolicy::Forbid => "Forbid",
            ConcurrencyPolicy::Replace => "Replace",
        }
    }
}

/// Specification for creating a Kubernetes CronJob.
///
/// CronJobs create Jobs on a repeating schedule, similar to Unix cron.
///
/// # Examples
///
/// ```no_run
/// use lmrc_kubernetes::cronjob::{CronJobSpec, ConcurrencyPolicy};
/// use lmrc_kubernetes::job::{JobSpec, RestartPolicy};
/// use lmrc_kubernetes::ContainerSpec;
///
/// let container = ContainerSpec::new("backup", "backup-tool:v1")
///     .with_command(vec!["sh".to_string(), "-c".to_string()])
///     .with_args(vec!["backup.sh".to_string()]);
///
/// let job_template = JobSpec::new("backup")
///     .with_container(container)
///     .with_restart_policy(RestartPolicy::OnFailure);
///
/// let cronjob = CronJobSpec::new("daily-backup", "0 2 * * *")
///     .with_job_template(job_template)
///     .with_concurrency_policy(ConcurrencyPolicy::Forbid)
///     .with_successful_jobs_history_limit(3);
/// ```
#[derive(Debug, Clone)]
pub struct CronJobSpec {
    name: String,
    schedule: String,
    job_template: Option<JobSpec>,
    concurrency_policy: ConcurrencyPolicy,
    successful_jobs_history_limit: Option<i32>,
    failed_jobs_history_limit: Option<i32>,
    starting_deadline_seconds: Option<i64>,
    suspend: bool,
}

impl CronJobSpec {
    /// Creates a new CronJobSpec with the given name and schedule.
    ///
    /// # Arguments
    ///
    /// * `name` - The name of the CronJob
    /// * `schedule` - Cron schedule (e.g., "0 2 * * *" for 2am daily)
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::cronjob::CronJobSpec;
    ///
    /// // Run every day at 2am
    /// let cronjob = CronJobSpec::new("daily-task", "0 2 * * *");
    ///
    /// // Run every hour
    /// let cronjob = CronJobSpec::new("hourly-task", "0 * * * *");
    ///
    /// // Run every 15 minutes
    /// let cronjob = CronJobSpec::new("frequent-task", "*/15 * * * *");
    /// ```
    pub fn new(name: impl Into<String>, schedule: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            schedule: schedule.into(),
            job_template: None,
            concurrency_policy: ConcurrencyPolicy::Allow,
            successful_jobs_history_limit: None,
            failed_jobs_history_limit: None,
            starting_deadline_seconds: None,
            suspend: false,
        }
    }

    /// Sets the job template.
    ///
    /// This defines the Job that will be created on each schedule.
    pub fn with_job_template(mut self, template: JobSpec) -> Self {
        self.job_template = Some(template);
        self
    }

    /// Sets the concurrency policy.
    pub fn with_concurrency_policy(mut self, policy: ConcurrencyPolicy) -> Self {
        self.concurrency_policy = policy;
        self
    }

    /// Sets the number of successful finished jobs to keep.
    ///
    /// Defaults to 3 if not set.
    pub fn with_successful_jobs_history_limit(mut self, limit: i32) -> Self {
        self.successful_jobs_history_limit = Some(limit);
        self
    }

    /// Sets the number of failed finished jobs to keep.
    ///
    /// Defaults to 1 if not set.
    pub fn with_failed_jobs_history_limit(mut self, limit: i32) -> Self {
        self.failed_jobs_history_limit = Some(limit);
        self
    }

    /// Sets the deadline in seconds for starting the job.
    ///
    /// If a job misses its scheduled time by more than this deadline,
    /// it will be considered a failed job.
    pub fn with_starting_deadline_seconds(mut self, seconds: i64) -> Self {
        self.starting_deadline_seconds = Some(seconds);
        self
    }

    /// Suspends the CronJob.
    ///
    /// If set to true, all subsequent executions are suspended.
    pub fn suspended(mut self, suspend: bool) -> Self {
        self.suspend = suspend;
        self
    }

    /// Validates the CronJob specification.
    ///
    /// # Returns
    ///
    /// Returns `Ok(())` if valid, or an error message if invalid.
    pub fn validate(&self) -> Result<(), String> {
        if self.name.is_empty() {
            return Err("CronJob name cannot be empty".to_string());
        }

        if self.schedule.is_empty() {
            return Err("CronJob schedule cannot be empty".to_string());
        }

        if self.job_template.is_none() {
            return Err("CronJob must have a job template".to_string());
        }

        // Validate job template
        if let Some(ref template) = self.job_template {
            template.validate()?;
        }

        Ok(())
    }

    // Getters

    /// Returns the CronJob name.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Returns the schedule.
    pub fn schedule(&self) -> &str {
        &self.schedule
    }

    /// Returns the job template.
    pub fn job_template(&self) -> Option<&JobSpec> {
        self.job_template.as_ref()
    }

    /// Returns the concurrency policy.
    pub fn concurrency_policy(&self) -> &ConcurrencyPolicy {
        &self.concurrency_policy
    }

    /// Returns the successful jobs history limit.
    pub fn successful_jobs_history_limit(&self) -> Option<i32> {
        self.successful_jobs_history_limit
    }

    /// Returns the failed jobs history limit.
    pub fn failed_jobs_history_limit(&self) -> Option<i32> {
        self.failed_jobs_history_limit
    }

    /// Returns the starting deadline in seconds.
    pub fn starting_deadline_seconds(&self) -> Option<i64> {
        self.starting_deadline_seconds
    }

    /// Returns whether the CronJob is suspended.
    pub fn is_suspended(&self) -> bool {
        self.suspend
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::deployment::ContainerSpec;

    #[test]
    fn test_new_cronjob() {
        let cronjob = CronJobSpec::new("test", "0 2 * * *");
        assert_eq!(cronjob.name(), "test");
        assert_eq!(cronjob.schedule(), "0 2 * * *");
        assert!(!cronjob.is_suspended());
    }

    #[test]
    fn test_with_job_template() {
        let container = ContainerSpec::new("task", "busybox");
        let job = JobSpec::new("job").with_container(container);

        let cronjob = CronJobSpec::new("test", "0 * * * *").with_job_template(job);

        assert!(cronjob.job_template().is_some());
    }

    #[test]
    fn test_concurrency_policy() {
        let cronjob = CronJobSpec::new("test", "0 * * * *")
            .with_concurrency_policy(ConcurrencyPolicy::Forbid);

        assert_eq!(cronjob.concurrency_policy(), &ConcurrencyPolicy::Forbid);
    }

    #[test]
    fn test_validation() {
        let cronjob = CronJobSpec::new("", "0 * * * *");
        assert!(cronjob.validate().is_err());

        let cronjob = CronJobSpec::new("test", "");
        assert!(cronjob.validate().is_err());

        let cronjob = CronJobSpec::new("test", "0 * * * *");
        assert!(cronjob.validate().is_err()); // No job template

        let container = ContainerSpec::new("task", "busybox");
        let job = JobSpec::new("job").with_container(container);
        let cronjob = CronJobSpec::new("test", "0 * * * *").with_job_template(job);
        assert!(cronjob.validate().is_ok());
    }
}
