//! PersistentVolumeClaim specification types.

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

/// Access mode for a PersistentVolumeClaim.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AccessMode {
    /// Volume can be mounted as read-write by a single node.
    ReadWriteOnce,
    /// Volume can be mounted as read-only by many nodes.
    ReadOnlyMany,
    /// Volume can be mounted as read-write by many nodes.
    ReadWriteMany,
    /// Volume can be mounted as read-write by a single pod (CSI only).
    ReadWriteOncePod,
}

impl AccessMode {
    /// Convert to Kubernetes API string.
    pub fn to_k8s_string(&self) -> &'static str {
        match self {
            AccessMode::ReadWriteOnce => "ReadWriteOnce",
            AccessMode::ReadOnlyMany => "ReadOnlyMany",
            AccessMode::ReadWriteMany => "ReadWriteMany",
            AccessMode::ReadWriteOncePod => "ReadWriteOncePod",
        }
    }
}

/// Storage class specification.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum StorageClass {
    /// Use the default storage class.
    Default,
    /// Use a specific storage class by name.
    Named(String),
}

impl StorageClass {
    /// Get the storage class name, if any.
    pub fn name(&self) -> Option<&str> {
        match self {
            StorageClass::Default => None,
            StorageClass::Named(name) => Some(name),
        }
    }
}

/// Specification for a PersistentVolumeClaim.
///
/// # Examples
///
/// ```
/// use lmrc_kubernetes::pvc::{PersistentVolumeClaimSpec, AccessMode, StorageClass};
///
/// // Simple PVC with default storage class
/// let pvc = PersistentVolumeClaimSpec::new("data-volume", "10Gi")
///     .with_access_mode(AccessMode::ReadWriteOnce);
///
/// // PVC with specific storage class
/// let pvc = PersistentVolumeClaimSpec::new("fast-storage", "100Gi")
///     .with_access_mode(AccessMode::ReadWriteOnce)
///     .with_storage_class(StorageClass::Named("fast-ssd".to_string()));
///
/// // PVC with multiple access modes
/// let pvc = PersistentVolumeClaimSpec::new("shared-data", "50Gi")
///     .with_access_mode(AccessMode::ReadWriteMany)
///     .with_access_mode(AccessMode::ReadOnlyMany);
/// ```
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PersistentVolumeClaimSpec {
    name: String,
    storage: String,
    access_modes: Vec<AccessMode>,
    storage_class: StorageClass,
    labels: HashMap<String, String>,
    selector: Option<HashMap<String, String>>,
    volume_name: Option<String>,
}

impl PersistentVolumeClaimSpec {
    /// Create a new PersistentVolumeClaimSpec.
    ///
    /// # Arguments
    ///
    /// * `name` - Name of the PVC
    /// * `storage` - Storage size (e.g., "10Gi", "100Mi")
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::pvc::PersistentVolumeClaimSpec;
    ///
    /// let pvc = PersistentVolumeClaimSpec::new("my-data", "10Gi");
    /// ```
    pub fn new(name: impl Into<String>, storage: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            storage: storage.into(),
            access_modes: vec![],
            storage_class: StorageClass::Default,
            labels: HashMap::new(),
            selector: None,
            volume_name: None,
        }
    }

    /// Add an access mode to the PVC.
    pub fn with_access_mode(mut self, mode: AccessMode) -> Self {
        self.access_modes.push(mode);
        self
    }

    /// Set the storage class for the PVC.
    pub fn with_storage_class(mut self, storage_class: StorageClass) -> Self {
        self.storage_class = storage_class;
        self
    }

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

    /// Set a label selector for binding to specific PVs.
    pub fn with_selector(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        if self.selector.is_none() {
            self.selector = Some(HashMap::new());
        }
        self.selector
            .as_mut()
            .unwrap()
            .insert(key.into(), value.into());
        self
    }

    /// Bind to a specific PersistentVolume by name.
    pub fn with_volume_name(mut self, name: impl Into<String>) -> Self {
        self.volume_name = Some(name.into());
        self
    }

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

    /// Get the storage size.
    pub fn storage(&self) -> &str {
        &self.storage
    }

    /// Get the access modes.
    pub fn access_modes(&self) -> &[AccessMode] {
        &self.access_modes
    }

    /// Get the storage class.
    pub fn storage_class(&self) -> &StorageClass {
        &self.storage_class
    }

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

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

    /// Get the volume name.
    pub fn volume_name(&self) -> Option<&str> {
        self.volume_name.as_deref()
    }

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

        if self.storage.is_empty() {
            return Err("Storage size cannot be empty".to_string());
        }

        if self.access_modes.is_empty() {
            return Err("At least one access mode must be specified".to_string());
        }

        Ok(())
    }
}

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

    #[test]
    fn test_new_pvc() {
        let pvc = PersistentVolumeClaimSpec::new("data", "10Gi");
        assert_eq!(pvc.name(), "data");
        assert_eq!(pvc.storage(), "10Gi");
        assert!(pvc.access_modes().is_empty());
    }

    #[test]
    fn test_with_access_mode() {
        let pvc = PersistentVolumeClaimSpec::new("data", "10Gi")
            .with_access_mode(AccessMode::ReadWriteOnce);

        assert_eq!(pvc.access_modes().len(), 1);
        assert_eq!(pvc.access_modes()[0], AccessMode::ReadWriteOnce);
    }

    #[test]
    fn test_with_storage_class() {
        let pvc = PersistentVolumeClaimSpec::new("data", "10Gi")
            .with_storage_class(StorageClass::Named("fast-ssd".to_string()));

        assert_eq!(pvc.storage_class().name(), Some("fast-ssd"));
    }

    #[test]
    fn test_validation_empty_name() {
        let pvc = PersistentVolumeClaimSpec::new("", "10Gi");
        assert!(pvc.validate().is_err());
    }

    #[test]
    fn test_validation_no_access_modes() {
        let pvc = PersistentVolumeClaimSpec::new("data", "10Gi");
        assert!(pvc.validate().is_err());
    }

    #[test]
    fn test_validation_valid() {
        let pvc = PersistentVolumeClaimSpec::new("data", "10Gi")
            .with_access_mode(AccessMode::ReadWriteOnce);
        assert!(pvc.validate().is_ok());
    }

    #[test]
    fn test_access_mode_conversion() {
        assert_eq!(AccessMode::ReadWriteOnce.to_k8s_string(), "ReadWriteOnce");
        assert_eq!(AccessMode::ReadOnlyMany.to_k8s_string(), "ReadOnlyMany");
        assert_eq!(AccessMode::ReadWriteMany.to_k8s_string(), "ReadWriteMany");
        assert_eq!(
            AccessMode::ReadWriteOncePod.to_k8s_string(),
            "ReadWriteOncePod"
        );
    }
}
