//! Deployment specification types.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Specification for a Kubernetes Deployment.
///
/// # Examples
///
/// ```
/// use lmrc_kubernetes::deployment::{DeploymentSpec, ContainerSpec};
///
/// let container = ContainerSpec::new("myapp", "myregistry/myapp:v1.0.0")
///     .with_port(8080)
///     .with_env("DATABASE_URL", "postgres://...");
///
/// let deployment = DeploymentSpec::new("my-deployment")
///     .with_replicas(3)
///     .with_container(container);
/// ```
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DeploymentSpec {
    name: String,
    replicas: i32,
    containers: Vec<ContainerSpec>,
    init_containers: Vec<ContainerSpec>,
    strategy: DeploymentStrategy,
    labels: HashMap<String, String>,
    image_pull_secrets: Vec<String>,
    service_account: Option<String>,
    volumes: Vec<Volume>,
}

impl DeploymentSpec {
    /// Create a new deployment specification.
    ///
    /// # Arguments
    ///
    /// * `name` - Name of the deployment
    pub fn new<S: Into<String>>(name: S) -> Self {
        let name = name.into();
        let mut labels = HashMap::new();
        labels.insert("app".to_string(), name.clone());

        Self {
            name,
            replicas: 1,
            containers: Vec::new(),
            init_containers: Vec::new(),
            strategy: DeploymentStrategy::Recreate,
            labels,
            image_pull_secrets: Vec::new(),
            service_account: None,
            volumes: Vec::new(),
        }
    }

    /// Set the number of replicas.
    pub fn with_replicas(mut self, replicas: i32) -> Self {
        self.replicas = replicas;
        // Auto-select strategy based on replica count
        if replicas > 1 {
            self.strategy = DeploymentStrategy::RollingUpdate {
                max_surge: Some("50%".to_string()),
                max_unavailable: Some("0".to_string()),
            };
        }
        self
    }

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

    /// Set the deployment strategy.
    pub fn with_strategy(mut self, strategy: DeploymentStrategy) -> Self {
        self.strategy = strategy;
        self
    }

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

    /// Add multiple labels to the deployment.
    pub fn with_labels(mut self, labels: HashMap<String, String>) -> Self {
        self.labels.extend(labels);
        self
    }

    /// Add an init container to the deployment.
    ///
    /// Init containers run before the main containers and must complete successfully.
    pub fn with_init_container(mut self, container: ContainerSpec) -> Self {
        self.init_containers.push(container);
        self
    }

    /// Add an image pull secret for private registries.
    ///
    /// # Arguments
    ///
    /// * `secret_name` - Name of the secret containing registry credentials
    pub fn with_image_pull_secret<S: Into<String>>(mut self, secret_name: S) -> Self {
        self.image_pull_secrets.push(secret_name.into());
        self
    }

    /// Set the service account for the pods.
    ///
    /// # Arguments
    ///
    /// * `account` - Name of the service account
    pub fn with_service_account<S: Into<String>>(mut self, account: S) -> Self {
        self.service_account = Some(account.into());
        self
    }

    /// Add a volume to the deployment.
    ///
    /// Volumes can be mounted into containers using `ContainerSpec::with_volume_mount()`.
    ///
    /// # Arguments
    ///
    /// * `volume` - The volume to add
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::deployment::{DeploymentSpec, Volume, VolumeMount, ContainerSpec};
    ///
    /// let volume = Volume::from_config_map("config-vol", "app-config");
    /// let mount = VolumeMount::new("config-vol", "/etc/config");
    ///
    /// let container = ContainerSpec::new("app", "myapp:v1")
    ///     .with_volume_mount(mount);
    ///
    /// let deployment = DeploymentSpec::new("my-app")
    ///     .with_volume(volume)
    ///     .with_container(container);
    /// ```
    pub fn with_volume(mut self, volume: Volume) -> Self {
        self.volumes.push(volume);
        self
    }

    /// Validate the deployment spec.
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - No containers are specified
    /// - Replicas is negative
    pub fn validate(&self) -> Result<(), String> {
        if self.containers.is_empty() {
            return Err("Deployment must have at least one container".to_string());
        }
        if self.replicas < 0 {
            return Err("Replicas cannot be negative".to_string());
        }
        for container in &self.containers {
            container.validate()?;
        }
        for init_container in &self.init_containers {
            init_container.validate()?;
        }
        Ok(())
    }

    /// Get the deployment name.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Get the number of replicas.
    pub fn replicas(&self) -> i32 {
        self.replicas
    }

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

    /// Get the deployment strategy.
    pub fn strategy(&self) -> &DeploymentStrategy {
        &self.strategy
    }

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

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

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

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

    /// Get the volumes.
    pub fn volumes(&self) -> &[Volume] {
        &self.volumes
    }
}

/// Container specification for a pod.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ContainerSpec {
    name: String,
    image: String,
    ports: Vec<i32>,
    env: HashMap<String, String>,
    resources: Option<ResourceRequirements>,
    liveness_probe: Option<ProbeSpec>,
    readiness_probe: Option<ProbeSpec>,
    startup_probe: Option<ProbeSpec>,
    volume_mounts: Vec<VolumeMount>,
    command: Option<Vec<String>>,
    args: Option<Vec<String>>,
}

impl ContainerSpec {
    /// Create a new container specification.
    ///
    /// # Arguments
    ///
    /// * `name` - Name of the container
    /// * `image` - Container image (e.g., "nginx:1.21")
    pub fn new<S: Into<String>>(name: S, image: S) -> Self {
        Self {
            name: name.into(),
            image: image.into(),
            ports: Vec::new(),
            env: HashMap::new(),
            resources: None,
            liveness_probe: None,
            readiness_probe: None,
            startup_probe: None,
            volume_mounts: Vec::new(),
            command: None,
            args: None,
        }
    }

    /// Add a container port.
    pub fn with_port(mut self, port: i32) -> Self {
        self.ports.push(port);
        self
    }

    /// Add an environment variable.
    pub fn with_env<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
        self.env.insert(key.into(), value.into());
        self
    }

    /// Add multiple environment variables.
    pub fn with_env_map(mut self, env: HashMap<String, String>) -> Self {
        self.env.extend(env);
        self
    }

    /// Set resource requirements.
    pub fn with_resources(mut self, resources: ResourceRequirements) -> Self {
        self.resources = Some(resources);
        self
    }

    /// Set liveness probe.
    ///
    /// Liveness probes determine when to restart a container.
    pub fn with_liveness_probe(mut self, probe: ProbeSpec) -> Self {
        self.liveness_probe = Some(probe);
        self
    }

    /// Set readiness probe.
    ///
    /// Readiness probes determine when a container is ready to accept traffic.
    pub fn with_readiness_probe(mut self, probe: ProbeSpec) -> Self {
        self.readiness_probe = Some(probe);
        self
    }

    /// Set startup probe.
    ///
    /// Startup probes determine when a container has started.
    pub fn with_startup_probe(mut self, probe: ProbeSpec) -> Self {
        self.startup_probe = Some(probe);
        self
    }

    /// Add a volume mount.
    pub fn with_volume_mount(mut self, mount: VolumeMount) -> Self {
        self.volume_mounts.push(mount);
        self
    }

    /// Set container command (overrides ENTRYPOINT).
    pub fn with_command(mut self, command: Vec<String>) -> Self {
        self.command = Some(command);
        self
    }

    /// Set container arguments (overrides CMD).
    pub fn with_args(mut self, args: Vec<String>) -> Self {
        self.args = Some(args);
        self
    }

    /// Validate the container spec.
    pub fn validate(&self) -> Result<(), String> {
        if self.name.is_empty() {
            return Err("Container name cannot be empty".to_string());
        }
        if self.image.is_empty() {
            return Err("Container image cannot be empty".to_string());
        }
        Ok(())
    }

    /// Get the container name.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Get the container image.
    pub fn image(&self) -> &str {
        &self.image
    }

    /// Get the container ports.
    pub fn ports(&self) -> &[i32] {
        &self.ports
    }

    /// Get the environment variables.
    pub fn env(&self) -> &HashMap<String, String> {
        &self.env
    }

    /// Get the resource requirements.
    pub fn resources(&self) -> Option<&ResourceRequirements> {
        self.resources.as_ref()
    }

    /// Get the liveness probe.
    pub fn liveness_probe(&self) -> Option<&ProbeSpec> {
        self.liveness_probe.as_ref()
    }

    /// Get the readiness probe.
    pub fn readiness_probe(&self) -> Option<&ProbeSpec> {
        self.readiness_probe.as_ref()
    }

    /// Get the startup probe.
    pub fn startup_probe(&self) -> Option<&ProbeSpec> {
        self.startup_probe.as_ref()
    }

    /// Get the volume mounts.
    pub fn volume_mounts(&self) -> &[VolumeMount] {
        &self.volume_mounts
    }

    /// Get the command.
    pub fn command(&self) -> Option<&[String]> {
        self.command.as_deref()
    }

    /// Get the args.
    pub fn args(&self) -> Option<&[String]> {
        self.args.as_deref()
    }
}

/// Deployment strategy configuration.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum DeploymentStrategy {
    /// Recreate: terminate old pods before creating new ones.
    Recreate,
    /// RollingUpdate: gradually replace old pods with new ones.
    RollingUpdate {
        /// Maximum number of pods above desired replicas during update.
        max_surge: Option<String>,
        /// Maximum number of pods that can be unavailable during update.
        max_unavailable: Option<String>,
    },
}

/// Resource requirements for a container.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ResourceRequirements {
    cpu_request: Option<String>,
    cpu_limit: Option<String>,
    memory_request: Option<String>,
    memory_limit: Option<String>,
}

impl ResourceRequirements {
    /// Create new resource requirements.
    pub fn new() -> Self {
        Self {
            cpu_request: None,
            cpu_limit: None,
            memory_request: None,
            memory_limit: None,
        }
    }

    /// Set CPU request (e.g., "100m", "0.5").
    pub fn cpu_request<S: Into<String>>(mut self, request: S) -> Self {
        self.cpu_request = Some(request.into());
        self
    }

    /// Set CPU limit (e.g., "1", "2000m").
    pub fn cpu_limit<S: Into<String>>(mut self, limit: S) -> Self {
        self.cpu_limit = Some(limit.into());
        self
    }

    /// Set memory request (e.g., "128Mi", "1Gi").
    pub fn memory_request<S: Into<String>>(mut self, request: S) -> Self {
        self.memory_request = Some(request.into());
        self
    }

    /// Set memory limit (e.g., "256Mi", "2Gi").
    pub fn memory_limit<S: Into<String>>(mut self, limit: S) -> Self {
        self.memory_limit = Some(limit.into());
        self
    }

    /// Get CPU request.
    pub fn get_cpu_request(&self) -> Option<&str> {
        self.cpu_request.as_deref()
    }

    /// Get CPU limit.
    pub fn get_cpu_limit(&self) -> Option<&str> {
        self.cpu_limit.as_deref()
    }

    /// Get memory request.
    pub fn get_memory_request(&self) -> Option<&str> {
        self.memory_request.as_deref()
    }

    /// Get memory limit.
    pub fn get_memory_limit(&self) -> Option<&str> {
        self.memory_limit.as_deref()
    }
}

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

/// Probe specification for health checks.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProbeSpec {
    /// HTTP GET probe configuration.
    pub http_get: Option<HttpGetProbe>,
    /// Exec command probe configuration.
    pub exec: Option<ExecProbe>,
    /// TCP socket probe configuration.
    pub tcp_socket: Option<TcpSocketProbe>,
    /// Initial delay before probing (seconds).
    pub initial_delay_seconds: i32,
    /// How often to perform the probe (seconds).
    pub period_seconds: i32,
    /// Number of seconds after which the probe times out.
    pub timeout_seconds: i32,
    /// Minimum consecutive successes for the probe to be considered successful.
    pub success_threshold: i32,
    /// Minimum consecutive failures for the probe to be considered failed.
    pub failure_threshold: i32,
}

impl ProbeSpec {
    /// Create an HTTP GET probe.
    pub fn http_get(path: String, port: i32) -> Self {
        Self {
            http_get: Some(HttpGetProbe { path, port }),
            exec: None,
            tcp_socket: None,
            initial_delay_seconds: 0,
            period_seconds: 10,
            timeout_seconds: 1,
            success_threshold: 1,
            failure_threshold: 3,
        }
    }

    /// Create an exec command probe.
    pub fn exec(command: Vec<String>) -> Self {
        Self {
            http_get: None,
            exec: Some(ExecProbe { command }),
            tcp_socket: None,
            initial_delay_seconds: 0,
            period_seconds: 10,
            timeout_seconds: 1,
            success_threshold: 1,
            failure_threshold: 3,
        }
    }

    /// Create a TCP socket probe.
    pub fn tcp_socket(port: i32) -> Self {
        Self {
            http_get: None,
            exec: None,
            tcp_socket: Some(TcpSocketProbe { port }),
            initial_delay_seconds: 0,
            period_seconds: 10,
            timeout_seconds: 1,
            success_threshold: 1,
            failure_threshold: 3,
        }
    }

    /// Set initial delay in seconds.
    pub fn initial_delay_seconds(mut self, seconds: i32) -> Self {
        self.initial_delay_seconds = seconds;
        self
    }

    /// Set period in seconds.
    pub fn period_seconds(mut self, seconds: i32) -> Self {
        self.period_seconds = seconds;
        self
    }

    /// Set timeout in seconds.
    pub fn timeout_seconds(mut self, seconds: i32) -> Self {
        self.timeout_seconds = seconds;
        self
    }

    /// Set failure threshold.
    pub fn failure_threshold(mut self, threshold: i32) -> Self {
        self.failure_threshold = threshold;
        self
    }
}

/// HTTP GET probe configuration.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HttpGetProbe {
    /// Path to access on the HTTP server.
    pub path: String,
    /// Port to access on the container.
    pub port: i32,
}

/// Exec command probe configuration.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ExecProbe {
    /// Command to execute.
    pub command: Vec<String>,
}

/// TCP socket probe configuration.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TcpSocketProbe {
    /// Port to connect to.
    pub port: i32,
}

/// Volume mount specification.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VolumeMount {
    /// Name of the volume to mount.
    pub name: String,
    /// Path within the container at which the volume should be mounted.
    pub mount_path: String,
    /// Mounted read-only if true, read-write otherwise.
    pub read_only: bool,
    /// Path within the volume from which the container's volume should be mounted.
    pub sub_path: Option<String>,
}

impl VolumeMount {
    /// Create a new volume mount.
    pub fn new<S: Into<String>>(name: S, mount_path: S) -> Self {
        Self {
            name: name.into(),
            mount_path: mount_path.into(),
            read_only: false,
            sub_path: None,
        }
    }

    /// Set read-only flag.
    pub fn read_only(mut self, read_only: bool) -> Self {
        self.read_only = read_only;
        self
    }

    /// Set sub-path within the volume.
    pub fn sub_path<S: Into<String>>(mut self, path: S) -> Self {
        self.sub_path = Some(path.into());
        self
    }
}

/// Volume source types.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum VolumeSource {
    /// ConfigMap volume source.
    ConfigMap {
        /// Name of the ConfigMap.
        name: String,
        /// Optional mode bits for files.
        default_mode: Option<i32>,
        /// Optional list of items to project from the ConfigMap.
        items: Vec<KeyToPath>,
    },
    /// Secret volume source.
    Secret {
        /// Name of the Secret.
        secret_name: String,
        /// Optional mode bits for files.
        default_mode: Option<i32>,
        /// Optional list of items to project from the Secret.
        items: Vec<KeyToPath>,
    },
    /// EmptyDir volume source.
    EmptyDir {
        /// Medium (e.g., "Memory" for tmpfs).
        medium: Option<String>,
        /// Size limit.
        size_limit: Option<String>,
    },
    /// HostPath volume source (use with caution in production).
    HostPath {
        /// Path on the host.
        path: String,
        /// Type of the path (e.g., "Directory", "File").
        path_type: Option<String>,
    },
    /// PersistentVolumeClaim volume source.
    PersistentVolumeClaim {
        /// Name of the PersistentVolumeClaim.
        claim_name: String,
        /// Whether the volume should be read-only.
        read_only: bool,
    },
}

/// Mapping of key to path for ConfigMap and Secret volumes.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct KeyToPath {
    /// The key to project.
    pub key: String,
    /// The relative path for the file.
    pub path: String,
    /// Optional mode bits for the file.
    pub mode: Option<i32>,
}

impl KeyToPath {
    /// Create a new key to path mapping.
    pub fn new(key: impl Into<String>, path: impl Into<String>) -> Self {
        Self {
            key: key.into(),
            path: path.into(),
            mode: None,
        }
    }

    /// Set the file mode.
    pub fn with_mode(mut self, mode: i32) -> Self {
        self.mode = Some(mode);
        self
    }
}

/// Volume specification.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Volume {
    /// Name of the volume.
    pub name: String,
    /// Source of the volume.
    pub source: VolumeSource,
}

impl Volume {
    /// Create a new ConfigMap volume.
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::deployment::Volume;
    ///
    /// let volume = Volume::from_config_map("config-vol", "my-config");
    /// ```
    pub fn from_config_map(name: impl Into<String>, configmap_name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            source: VolumeSource::ConfigMap {
                name: configmap_name.into(),
                default_mode: None,
                items: Vec::new(),
            },
        }
    }

    /// Create a new ConfigMap volume with specific items.
    pub fn from_config_map_items(
        name: impl Into<String>,
        configmap_name: impl Into<String>,
        items: Vec<KeyToPath>,
    ) -> Self {
        Self {
            name: name.into(),
            source: VolumeSource::ConfigMap {
                name: configmap_name.into(),
                default_mode: None,
                items,
            },
        }
    }

    /// Create a new Secret volume.
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::deployment::Volume;
    ///
    /// let volume = Volume::from_secret("secret-vol", "my-secret");
    /// ```
    pub fn from_secret(name: impl Into<String>, secret_name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            source: VolumeSource::Secret {
                secret_name: secret_name.into(),
                default_mode: None,
                items: Vec::new(),
            },
        }
    }

    /// Create a new EmptyDir volume.
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::deployment::Volume;
    ///
    /// let volume = Volume::empty_dir("cache");
    /// ```
    pub fn empty_dir(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            source: VolumeSource::EmptyDir {
                medium: None,
                size_limit: None,
            },
        }
    }

    /// Create a new EmptyDir volume backed by tmpfs (in-memory).
    pub fn memory_volume(name: impl Into<String>, size_limit: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            source: VolumeSource::EmptyDir {
                medium: Some("Memory".to_string()),
                size_limit: Some(size_limit.into()),
            },
        }
    }

    /// Create a new HostPath volume.
    ///
    /// Warning: Use with caution in production environments.
    pub fn from_host_path(name: impl Into<String>, path: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            source: VolumeSource::HostPath {
                path: path.into(),
                path_type: None,
            },
        }
    }

    /// Create a new PersistentVolumeClaim volume.
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::deployment::Volume;
    ///
    /// let volume = Volume::from_pvc("data-vol", "my-pvc");
    /// ```
    pub fn from_pvc(name: impl Into<String>, claim_name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            source: VolumeSource::PersistentVolumeClaim {
                claim_name: claim_name.into(),
                read_only: false,
            },
        }
    }

    /// Create a new PersistentVolumeClaim volume (read-only).
    pub fn from_pvc_read_only(name: impl Into<String>, claim_name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            source: VolumeSource::PersistentVolumeClaim {
                claim_name: claim_name.into(),
                read_only: true,
            },
        }
    }

    /// Set the default mode for ConfigMap/Secret volumes.
    pub fn with_default_mode(mut self, mode: i32) -> Self {
        match &mut self.source {
            VolumeSource::ConfigMap { default_mode, .. } => {
                *default_mode = Some(mode);
            }
            VolumeSource::Secret { default_mode, .. } => {
                *default_mode = Some(mode);
            }
            _ => {}
        }
        self
    }
}

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

    #[test]
    fn test_deployment_spec_builder() {
        let container = ContainerSpec::new("app", "nginx:1.21")
            .with_port(80)
            .with_env("ENV", "production");

        let spec = DeploymentSpec::new("my-app")
            .with_replicas(3)
            .with_container(container);

        assert_eq!(spec.name(), "my-app");
        assert_eq!(spec.replicas(), 3);
        assert_eq!(spec.containers().len(), 1);
        assert!(matches!(
            spec.strategy(),
            DeploymentStrategy::RollingUpdate { .. }
        ));
    }

    #[test]
    fn test_container_spec_builder() {
        let mut env = HashMap::new();
        env.insert("DB_HOST".to_string(), "localhost".to_string());

        let container = ContainerSpec::new("api", "myapp:v1")
            .with_port(8080)
            .with_port(9090)
            .with_env_map(env);

        assert_eq!(container.name(), "api");
        assert_eq!(container.image(), "myapp:v1");
        assert_eq!(container.ports().len(), 2);
        assert_eq!(
            container.env().get("DB_HOST"),
            Some(&"localhost".to_string())
        );
    }

    #[test]
    fn test_resource_requirements() {
        let resources = ResourceRequirements::new()
            .cpu_request("100m")
            .cpu_limit("500m")
            .memory_request("128Mi")
            .memory_limit("512Mi");

        assert_eq!(resources.get_cpu_request(), Some("100m"));
        assert_eq!(resources.get_cpu_limit(), Some("500m"));
        assert_eq!(resources.get_memory_request(), Some("128Mi"));
        assert_eq!(resources.get_memory_limit(), Some("512Mi"));
    }

    #[test]
    fn test_deployment_strategy_auto_selection() {
        let spec1 = DeploymentSpec::new("app1").with_replicas(1);
        assert!(matches!(spec1.strategy(), DeploymentStrategy::Recreate));

        let spec2 = DeploymentSpec::new("app2").with_replicas(3);
        assert!(matches!(
            spec2.strategy(),
            DeploymentStrategy::RollingUpdate { .. }
        ));
    }
}
