//! NetworkPolicy specification builder.

use std::collections::HashMap;

/// Policy type - Ingress or Egress.
#[derive(Debug, Clone, PartialEq)]
pub enum PolicyType {
    /// Ingress traffic control.
    Ingress,
    /// Egress traffic control.
    Egress,
}

/// Port protocol.
#[derive(Debug, Clone, PartialEq)]
pub enum PortProtocol {
    /// TCP protocol.
    TCP,
    /// UDP protocol.
    UDP,
    /// SCTP protocol.
    SCTP,
}

impl PortProtocol {
    /// Convert to string representation.
    pub fn as_str(&self) -> &str {
        match self {
            PortProtocol::TCP => "TCP",
            PortProtocol::UDP => "UDP",
            PortProtocol::SCTP => "SCTP",
        }
    }
}

/// Network policy port specification.
#[derive(Debug, Clone)]
pub struct NetworkPolicyPort {
    /// Protocol (TCP, UDP, SCTP).
    pub protocol: PortProtocol,
    /// Port number.
    pub port: Option<i32>,
}

impl NetworkPolicyPort {
    /// Create a new port specification.
    pub fn new(protocol: PortProtocol) -> Self {
        Self {
            protocol,
            port: None,
        }
    }

    /// Set the port number.
    pub fn with_port(mut self, port: i32) -> Self {
        self.port = Some(port);
        self
    }
}

/// Peer specification for ingress/egress rules.
#[derive(Debug, Clone)]
pub struct NetworkPolicyPeer {
    /// Pod selector (match labels).
    pub pod_selector: Option<HashMap<String, String>>,
    /// Namespace selector (match labels).
    pub namespace_selector: Option<HashMap<String, String>>,
    /// IP block CIDR.
    pub ip_block_cidr: Option<String>,
    /// IP block exceptions.
    pub ip_block_except: Vec<String>,
}

impl NetworkPolicyPeer {
    /// Create a new peer from pod selector.
    pub fn from_pod_selector(labels: HashMap<String, String>) -> Self {
        Self {
            pod_selector: Some(labels),
            namespace_selector: None,
            ip_block_cidr: None,
            ip_block_except: Vec::new(),
        }
    }

    /// Create a new peer from namespace selector.
    pub fn from_namespace_selector(labels: HashMap<String, String>) -> Self {
        Self {
            pod_selector: None,
            namespace_selector: Some(labels),
            ip_block_cidr: None,
            ip_block_except: Vec::new(),
        }
    }

    /// Create a new peer from IP block.
    pub fn from_ip_block(cidr: impl Into<String>) -> Self {
        Self {
            pod_selector: None,
            namespace_selector: None,
            ip_block_cidr: Some(cidr.into()),
            ip_block_except: Vec::new(),
        }
    }

    /// Add IP block exception.
    pub fn with_exception(mut self, cidr: impl Into<String>) -> Self {
        self.ip_block_except.push(cidr.into());
        self
    }
}

/// Network policy rule (ingress or egress).
#[derive(Debug, Clone)]
pub struct PolicyRule {
    /// Peers (sources for ingress, destinations for egress).
    pub peers: Vec<NetworkPolicyPeer>,
    /// Ports allowed.
    pub ports: Vec<NetworkPolicyPort>,
}

impl PolicyRule {
    /// Create a new empty rule.
    pub fn new() -> Self {
        Self {
            peers: Vec::new(),
            ports: Vec::new(),
        }
    }

    /// Add a peer to the rule.
    pub fn with_peer(mut self, peer: NetworkPolicyPeer) -> Self {
        self.peers.push(peer);
        self
    }

    /// Add a port to the rule.
    pub fn with_port(mut self, port: NetworkPolicyPort) -> Self {
        self.ports.push(port);
        self
    }
}

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

/// Specification for creating a Kubernetes NetworkPolicy.
///
/// NetworkPolicies control network traffic to and from pods based on labels,
/// namespaces, and IP addresses.
///
/// # Examples
///
/// ```no_run
/// use lmrc_kubernetes::networkpolicy::{NetworkPolicySpec, PolicyRule, NetworkPolicyPeer, NetworkPolicyPort, PortProtocol};
/// use std::collections::HashMap;
///
/// // Allow traffic from specific pods on port 8080
/// let mut pod_selector = HashMap::new();
/// pod_selector.insert("app".to_string(), "frontend".to_string());
///
/// let peer = NetworkPolicyPeer::from_pod_selector(pod_selector);
/// let port = NetworkPolicyPort::new(PortProtocol::TCP).with_port(8080);
///
/// let rule = PolicyRule::new()
///     .with_peer(peer)
///     .with_port(port);
///
/// let mut selector = HashMap::new();
/// selector.insert("app".to_string(), "backend".to_string());
///
/// let policy = NetworkPolicySpec::new("backend-policy")
///     .with_pod_selector(selector)
///     .with_ingress_rule(rule);
/// ```
#[derive(Debug, Clone)]
pub struct NetworkPolicySpec {
    name: String,
    pod_selector: HashMap<String, String>,
    policy_types: Vec<PolicyType>,
    ingress_rules: Vec<PolicyRule>,
    egress_rules: Vec<PolicyRule>,
    labels: HashMap<String, String>,
}

impl NetworkPolicySpec {
    /// Creates a new NetworkPolicySpec with the given name.
    ///
    /// # Arguments
    ///
    /// * `name` - The name of the NetworkPolicy
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::networkpolicy::NetworkPolicySpec;
    ///
    /// let policy = NetworkPolicySpec::new("my-policy");
    /// ```
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            pod_selector: HashMap::new(),
            policy_types: Vec::new(),
            ingress_rules: Vec::new(),
            egress_rules: Vec::new(),
            labels: HashMap::new(),
        }
    }

    /// Sets the pod selector.
    ///
    /// This determines which pods the policy applies to.
    ///
    /// # Arguments
    ///
    /// * `selector` - Label selector for pods
    pub fn with_pod_selector(mut self, selector: HashMap<String, String>) -> Self {
        self.pod_selector = selector;
        self
    }

    /// Adds an ingress rule.
    ///
    /// # Arguments
    ///
    /// * `rule` - The ingress rule to add
    pub fn with_ingress_rule(mut self, rule: PolicyRule) -> Self {
        if !self.policy_types.contains(&PolicyType::Ingress) {
            self.policy_types.push(PolicyType::Ingress);
        }
        self.ingress_rules.push(rule);
        self
    }

    /// Adds an egress rule.
    ///
    /// # Arguments
    ///
    /// * `rule` - The egress rule to add
    pub fn with_egress_rule(mut self, rule: PolicyRule) -> Self {
        if !self.policy_types.contains(&PolicyType::Egress) {
            self.policy_types.push(PolicyType::Egress);
        }
        self.egress_rules.push(rule);
        self
    }

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

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

        if self.policy_types.is_empty() {
            return Err(
                "NetworkPolicy must have at least one policy type (ingress or egress)".to_string(),
            );
        }

        Ok(())
    }

    // Getters

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

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

    /// Returns the policy types.
    pub fn policy_types(&self) -> &[PolicyType] {
        &self.policy_types
    }

    /// Returns the ingress rules.
    pub fn ingress_rules(&self) -> &[PolicyRule] {
        &self.ingress_rules
    }

    /// Returns the egress rules.
    pub fn egress_rules(&self) -> &[PolicyRule] {
        &self.egress_rules
    }

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

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

    #[test]
    fn test_new_network_policy() {
        let policy = NetworkPolicySpec::new("test-policy");
        assert_eq!(policy.name(), "test-policy");
        assert!(policy.ingress_rules().is_empty());
        assert!(policy.egress_rules().is_empty());
    }

    #[test]
    fn test_with_ingress_rule() {
        let rule = PolicyRule::new();
        let policy = NetworkPolicySpec::new("test").with_ingress_rule(rule);

        assert_eq!(policy.ingress_rules().len(), 1);
        assert!(policy.policy_types().contains(&PolicyType::Ingress));
    }

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

        let policy = NetworkPolicySpec::new("test");
        assert!(policy.validate().is_err()); // No policy types

        let rule = PolicyRule::new();
        let policy = NetworkPolicySpec::new("test").with_ingress_rule(rule);
        assert!(policy.validate().is_ok());
    }

    #[test]
    fn test_network_policy_peer() {
        let mut labels = HashMap::new();
        labels.insert("app".to_string(), "web".to_string());

        let peer = NetworkPolicyPeer::from_pod_selector(labels.clone());
        assert_eq!(peer.pod_selector, Some(labels));

        let peer = NetworkPolicyPeer::from_ip_block("10.0.0.0/24").with_exception("10.0.0.1/32");
        assert_eq!(peer.ip_block_cidr, Some("10.0.0.0/24".to_string()));
        assert_eq!(peer.ip_block_except.len(), 1);
    }
}
