//! HorizontalPodAutoscaler specification types.

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

/// Reference to the resource being scaled.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ScaleTargetRef {
    /// API version of the target resource.
    pub api_version: String,
    /// Kind of the target resource (e.g., "Deployment", "StatefulSet").
    pub kind: String,
    /// Name of the target resource.
    pub name: String,
}

impl ScaleTargetRef {
    /// Create a new ScaleTargetRef for a Deployment.
    pub fn deployment(name: impl Into<String>) -> Self {
        Self {
            api_version: "apps/v1".to_string(),
            kind: "Deployment".to_string(),
            name: name.into(),
        }
    }

    /// Create a new ScaleTargetRef for a StatefulSet.
    pub fn stateful_set(name: impl Into<String>) -> Self {
        Self {
            api_version: "apps/v1".to_string(),
            kind: "StatefulSet".to_string(),
            name: name.into(),
        }
    }

    /// Create a new ScaleTargetRef for a ReplicaSet.
    pub fn replica_set(name: impl Into<String>) -> Self {
        Self {
            api_version: "apps/v1".to_string(),
            kind: "ReplicaSet".to_string(),
            name: name.into(),
        }
    }
}

/// Metric target type.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum MetricTarget {
    /// Target average utilization (percentage).
    Utilization(i32),
    /// Target average value.
    AverageValue(String),
    /// Target total value.
    Value(String),
}

/// Type of metric to use for scaling.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum MetricType {
    /// Resource metric (CPU or memory).
    Resource {
        /// Name of the resource (cpu or memory).
        name: String,
        /// Target metric value.
        target: MetricTarget,
    },
    /// Pod metric.
    Pods {
        /// Name of the metric.
        metric_name: String,
        /// Target average value.
        target_average_value: String,
    },
    /// Object metric.
    Object {
        /// Name of the metric.
        metric_name: String,
        /// Target object reference.
        target: ScaleTargetRef,
        /// Target value.
        target_value: String,
    },
    /// External metric.
    External {
        /// Name of the metric.
        metric_name: String,
        /// Target value.
        target_value: String,
    },
}

impl MetricType {
    /// Create a CPU utilization metric.
    ///
    /// # Arguments
    ///
    /// * `percent` - Target CPU utilization percentage (e.g., 80 for 80%)
    pub fn cpu_utilization(percent: i32) -> Self {
        MetricType::Resource {
            name: "cpu".to_string(),
            target: MetricTarget::Utilization(percent),
        }
    }

    /// Create a memory utilization metric.
    ///
    /// # Arguments
    ///
    /// * `percent` - Target memory utilization percentage
    pub fn memory_utilization(percent: i32) -> Self {
        MetricType::Resource {
            name: "memory".to_string(),
            target: MetricTarget::Utilization(percent),
        }
    }

    /// Create a CPU average value metric.
    ///
    /// # Arguments
    ///
    /// * `value` - Target CPU value (e.g., "500m" for 500 millicores)
    pub fn cpu_average_value(value: impl Into<String>) -> Self {
        MetricType::Resource {
            name: "cpu".to_string(),
            target: MetricTarget::AverageValue(value.into()),
        }
    }

    /// Create a memory average value metric.
    ///
    /// # Arguments
    ///
    /// * `value` - Target memory value (e.g., "1Gi")
    pub fn memory_average_value(value: impl Into<String>) -> Self {
        MetricType::Resource {
            name: "memory".to_string(),
            target: MetricTarget::AverageValue(value.into()),
        }
    }
}

/// Specification for a HorizontalPodAutoscaler.
///
/// # Examples
///
/// ```
/// use lmrc_kubernetes::hpa::{HpaSpec, ScaleTargetRef, MetricType};
///
/// // CPU-based autoscaling
/// let hpa = HpaSpec::new("web-hpa", ScaleTargetRef::deployment("web-app"))
///     .with_min_replicas(2)
///     .with_max_replicas(10)
///     .with_metric(MetricType::cpu_utilization(80));
///
/// // Memory-based autoscaling
/// let hpa = HpaSpec::new("api-hpa", ScaleTargetRef::deployment("api"))
///     .with_min_replicas(3)
///     .with_max_replicas(20)
///     .with_metric(MetricType::memory_utilization(75));
///
/// // Multiple metrics
/// let hpa = HpaSpec::new("app-hpa", ScaleTargetRef::deployment("app"))
///     .with_min_replicas(2)
///     .with_max_replicas(10)
///     .with_metric(MetricType::cpu_utilization(80))
///     .with_metric(MetricType::memory_utilization(75));
/// ```
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct HpaSpec {
    name: String,
    scale_target_ref: ScaleTargetRef,
    min_replicas: Option<i32>,
    max_replicas: i32,
    metrics: Vec<MetricType>,
    labels: HashMap<String, String>,
}

impl HpaSpec {
    /// Create a new HpaSpec.
    ///
    /// # Arguments
    ///
    /// * `name` - Name of the HPA
    /// * `scale_target_ref` - Reference to the resource to scale
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::hpa::{HpaSpec, ScaleTargetRef};
    ///
    /// let hpa = HpaSpec::new("my-hpa", ScaleTargetRef::deployment("my-app"));
    /// ```
    pub fn new(name: impl Into<String>, scale_target_ref: ScaleTargetRef) -> Self {
        Self {
            name: name.into(),
            scale_target_ref,
            min_replicas: None,
            max_replicas: 10,
            metrics: vec![],
            labels: HashMap::new(),
        }
    }

    /// Set the minimum number of replicas.
    pub fn with_min_replicas(mut self, min: i32) -> Self {
        self.min_replicas = Some(min);
        self
    }

    /// Set the maximum number of replicas.
    pub fn with_max_replicas(mut self, max: i32) -> Self {
        self.max_replicas = max;
        self
    }

    /// Add a metric for scaling decisions.
    pub fn with_metric(mut self, metric: MetricType) -> Self {
        self.metrics.push(metric);
        self
    }

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

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

    /// Get the scale target reference.
    pub fn scale_target_ref(&self) -> &ScaleTargetRef {
        &self.scale_target_ref
    }

    /// Get the minimum replicas.
    pub fn min_replicas(&self) -> Option<i32> {
        self.min_replicas
    }

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

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

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

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

        if self.max_replicas < 1 {
            return Err("Max replicas must be at least 1".to_string());
        }

        if let Some(min) = self.min_replicas {
            if min < 1 {
                return Err("Min replicas must be at least 1".to_string());
            }
            if min > self.max_replicas {
                return Err("Min replicas cannot be greater than max replicas".to_string());
            }
        }

        if self.metrics.is_empty() {
            return Err("At least one metric must be specified".to_string());
        }

        Ok(())
    }
}

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

    #[test]
    fn test_new_hpa() {
        let hpa = HpaSpec::new("test", ScaleTargetRef::deployment("app"));
        assert_eq!(hpa.name(), "test");
        assert_eq!(hpa.max_replicas(), 10);
        assert_eq!(hpa.min_replicas(), None);
    }

    #[test]
    fn test_with_replicas() {
        let hpa = HpaSpec::new("test", ScaleTargetRef::deployment("app"))
            .with_min_replicas(2)
            .with_max_replicas(20);

        assert_eq!(hpa.min_replicas(), Some(2));
        assert_eq!(hpa.max_replicas(), 20);
    }

    #[test]
    fn test_with_metric() {
        let hpa = HpaSpec::new("test", ScaleTargetRef::deployment("app"))
            .with_metric(MetricType::cpu_utilization(80));

        assert_eq!(hpa.metrics().len(), 1);
    }

    #[test]
    fn test_scale_target_ref() {
        let deploy_ref = ScaleTargetRef::deployment("app");
        assert_eq!(deploy_ref.kind, "Deployment");
        assert_eq!(deploy_ref.api_version, "apps/v1");
        assert_eq!(deploy_ref.name, "app");

        let stateful_ref = ScaleTargetRef::stateful_set("db");
        assert_eq!(stateful_ref.kind, "StatefulSet");
    }

    #[test]
    fn test_validation_empty_name() {
        let hpa = HpaSpec::new("", ScaleTargetRef::deployment("app"));
        assert!(hpa.validate().is_err());
    }

    #[test]
    fn test_validation_no_metrics() {
        let hpa = HpaSpec::new("test", ScaleTargetRef::deployment("app"));
        assert!(hpa.validate().is_err());
    }

    #[test]
    fn test_validation_valid() {
        let hpa = HpaSpec::new("test", ScaleTargetRef::deployment("app"))
            .with_min_replicas(2)
            .with_max_replicas(10)
            .with_metric(MetricType::cpu_utilization(80));

        assert!(hpa.validate().is_ok());
    }

    #[test]
    fn test_validation_min_greater_than_max() {
        let hpa = HpaSpec::new("test", ScaleTargetRef::deployment("app"))
            .with_min_replicas(10)
            .with_max_replicas(5)
            .with_metric(MetricType::cpu_utilization(80));

        assert!(hpa.validate().is_err());
    }

    #[test]
    fn test_metric_types() {
        let cpu = MetricType::cpu_utilization(80);
        let memory = MetricType::memory_utilization(75);
        let cpu_avg = MetricType::cpu_average_value("500m");
        let mem_avg = MetricType::memory_average_value("1Gi");

        match cpu {
            MetricType::Resource { name, .. } => assert_eq!(name, "cpu"),
            _ => panic!("Expected Resource metric"),
        }

        match memory {
            MetricType::Resource { name, .. } => assert_eq!(name, "memory"),
            _ => panic!("Expected Resource metric"),
        }

        assert!(matches!(cpu_avg, MetricType::Resource { .. }));
        assert!(matches!(mem_avg, MetricType::Resource { .. }));
    }
}
