//! Job specification builder.

use crate::deployment::ContainerSpec;
use std::collections::HashMap;

/// Restart policy for pods.
#[derive(Debug, Clone, PartialEq)]
pub enum RestartPolicy {
    /// Never restart the pod.
    Never,
    /// Restart on failure.
    OnFailure,
}

impl RestartPolicy {
    /// Convert to string representation.
    pub fn as_str(&self) -> &str {
        match self {
            RestartPolicy::Never => "Never",
            RestartPolicy::OnFailure => "OnFailure",
        }
    }
}

/// Specification for creating a Kubernetes Job.
///
/// Jobs create one or more pods and ensure that a specified number of them
/// successfully terminate.
///
/// # Examples
///
/// ```no_run
/// use lmrc_kubernetes::job::{JobSpec, RestartPolicy};
/// use lmrc_kubernetes::ContainerSpec;
///
/// let container = ContainerSpec::new("task", "busybox:latest")
///     .with_command(vec!["sh".to_string(), "-c".to_string()])
///     .with_args(vec!["echo hello && sleep 5".to_string()]);
///
/// let job = JobSpec::new("hello-job")
///     .with_container(container)
///     .with_completions(1)
///     .with_parallelism(1)
///     .with_restart_policy(RestartPolicy::Never);
/// ```
#[derive(Debug, Clone)]
pub struct JobSpec {
    name: String,
    containers: Vec<ContainerSpec>,
    init_containers: Vec<ContainerSpec>,
    completions: Option<i32>,
    parallelism: Option<i32>,
    backoff_limit: Option<i32>,
    active_deadline_seconds: Option<i64>,
    ttl_seconds_after_finished: Option<i32>,
    restart_policy: RestartPolicy,
    labels: HashMap<String, String>,
    image_pull_secrets: Vec<String>,
    service_account: Option<String>,
}

impl JobSpec {
    /// Creates a new JobSpec with the given name.
    ///
    /// # Arguments
    ///
    /// * `name` - The name of the Job
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::job::JobSpec;
    ///
    /// let job = JobSpec::new("my-job");
    /// ```
    pub fn new(name: impl Into<String>) -> Self {
        let name = name.into();
        let mut labels = HashMap::new();
        labels.insert("job".to_string(), name.clone());

        Self {
            name,
            containers: Vec::new(),
            init_containers: Vec::new(),
            completions: None,
            parallelism: None,
            backoff_limit: None,
            active_deadline_seconds: None,
            ttl_seconds_after_finished: None,
            restart_policy: RestartPolicy::Never,
            labels,
            image_pull_secrets: Vec::new(),
            service_account: None,
        }
    }

    /// Adds a container to the job.
    pub fn with_container(mut self, container: ContainerSpec) -> Self {
        self.containers.push(container);
        self
    }

    /// Adds an init container to the job.
    pub fn with_init_container(mut self, container: ContainerSpec) -> Self {
        self.init_containers.push(container);
        self
    }

    /// Sets the number of completions required.
    ///
    /// If not set, defaults to 1.
    pub fn with_completions(mut self, completions: i32) -> Self {
        self.completions = Some(completions);
        self
    }

    /// Sets the maximum number of pods running in parallel.
    ///
    /// If not set, defaults to 1.
    pub fn with_parallelism(mut self, parallelism: i32) -> Self {
        self.parallelism = Some(parallelism);
        self
    }

    /// Sets the number of retries before considering the job as failed.
    ///
    /// Defaults to 6 if not set.
    pub fn with_backoff_limit(mut self, limit: i32) -> Self {
        self.backoff_limit = Some(limit);
        self
    }

    /// Sets the maximum duration in seconds for the job.
    ///
    /// Job will be terminated if it exceeds this duration.
    pub fn with_active_deadline_seconds(mut self, seconds: i64) -> Self {
        self.active_deadline_seconds = Some(seconds);
        self
    }

    /// Sets TTL (time to live) for finished jobs.
    ///
    /// Job will be cleaned up after this many seconds after finishing.
    pub fn with_ttl_after_finished(mut self, seconds: i32) -> Self {
        self.ttl_seconds_after_finished = Some(seconds);
        self
    }

    /// Sets the restart policy.
    pub fn with_restart_policy(mut self, policy: RestartPolicy) -> Self {
        self.restart_policy = policy;
        self
    }

    /// Adds a label to the job.
    pub fn with_label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.labels.insert(key.into(), value.into());
        self
    }

    /// Adds an image pull secret for private registries.
    pub fn with_image_pull_secret(mut self, secret_name: impl Into<String>) -> Self {
        self.image_pull_secrets.push(secret_name.into());
        self
    }

    /// Sets the service account for the pods.
    pub fn with_service_account(mut self, account: impl Into<String>) -> Self {
        self.service_account = Some(account.into());
        self
    }

    /// Validates the job 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("Job name cannot be empty".to_string());
        }

        if self.containers.is_empty() {
            return Err("Job must have at least one container".to_string());
        }

        if let Some(completions) = self.completions
            && completions < 0
        {
            return Err("Completions cannot be negative".to_string());
        }

        if let Some(parallelism) = self.parallelism
            && parallelism < 0
        {
            return Err("Parallelism cannot be negative".to_string());
        }

        for container in &self.containers {
            container.validate()?;
        }

        Ok(())
    }

    // Getters

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

    /// Returns the containers.
    pub fn containers(&self) -> &[ContainerSpec] {
        &self.containers
    }

    /// Returns the init containers.
    pub fn init_containers(&self) -> &[ContainerSpec] {
        &self.init_containers
    }

    /// Returns the number of completions.
    pub fn completions(&self) -> Option<i32> {
        self.completions
    }

    /// Returns the parallelism.
    pub fn parallelism(&self) -> Option<i32> {
        self.parallelism
    }

    /// Returns the backoff limit.
    pub fn backoff_limit(&self) -> Option<i32> {
        self.backoff_limit
    }

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

    /// Returns the TTL after finished.
    pub fn ttl_seconds_after_finished(&self) -> Option<i32> {
        self.ttl_seconds_after_finished
    }

    /// Returns the restart policy.
    pub fn restart_policy(&self) -> &RestartPolicy {
        &self.restart_policy
    }

    /// Returns the labels.
    pub fn labels(&self) -> &HashMap<String, String> {
        &self.labels
    }

    /// Returns the image pull secrets.
    pub fn image_pull_secrets(&self) -> &[String] {
        &self.image_pull_secrets
    }

    /// Returns the service account.
    pub fn service_account(&self) -> Option<&str> {
        self.service_account.as_deref()
    }
}

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

    #[test]
    fn test_new_job() {
        let job = JobSpec::new("test-job");
        assert_eq!(job.name(), "test-job");
        assert!(job.containers().is_empty());
        assert_eq!(job.restart_policy(), &RestartPolicy::Never);
    }

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

        assert_eq!(job.containers().len(), 1);
    }

    #[test]
    fn test_with_parallelism() {
        let job = JobSpec::new("test")
            .with_completions(10)
            .with_parallelism(3);

        assert_eq!(job.completions(), Some(10));
        assert_eq!(job.parallelism(), Some(3));
    }

    #[test]
    fn test_validation() {
        let job = JobSpec::new("");
        assert!(job.validate().is_err());

        let job = JobSpec::new("test");
        assert!(job.validate().is_err()); // No containers

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