//! Ingress specification builder.

use std::collections::HashMap;

/// Path type for Ingress rules.
#[derive(Debug, Clone, PartialEq)]
pub enum PathType {
    /// Exact path matching.
    Exact,
    /// Prefix path matching.
    Prefix,
    /// Implementation-specific matching.
    ImplementationSpecific,
}

/// TLS configuration for Ingress.
#[derive(Debug, Clone)]
pub struct IngressTLS {
    /// Hosts covered by this TLS configuration.
    pub hosts: Vec<String>,
    /// Name of the Secret containing the TLS certificate and key.
    pub secret_name: String,
}

impl IngressTLS {
    /// Creates a new TLS configuration.
    ///
    /// # Arguments
    ///
    /// * `secret_name` - Name of the Secret containing TLS cert/key
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::ingress::IngressTLS;
    ///
    /// let tls = IngressTLS::new("my-tls-secret")
    ///     .with_host("example.com")
    ///     .with_host("www.example.com");
    /// ```
    pub fn new(secret_name: impl Into<String>) -> Self {
        Self {
            hosts: Vec::new(),
            secret_name: secret_name.into(),
        }
    }

    /// Adds a host to the TLS configuration.
    pub fn with_host(mut self, host: impl Into<String>) -> Self {
        self.hosts.push(host.into());
        self
    }
}

/// HTTP path for an Ingress rule.
#[derive(Debug, Clone)]
pub struct IngressPath {
    /// Path to match.
    pub path: String,
    /// Path matching type.
    pub path_type: PathType,
    /// Backend service name.
    pub service_name: String,
    /// Backend service port.
    pub service_port: i32,
}

impl IngressPath {
    /// Creates a new Ingress path.
    ///
    /// # Arguments
    ///
    /// * `path` - URL path to match
    /// * `service_name` - Name of the backend service
    /// * `service_port` - Port of the backend service
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::ingress::{IngressPath, PathType};
    ///
    /// let path = IngressPath::new("/api", "api-service", 8080)
    ///     .with_path_type(PathType::Prefix);
    /// ```
    pub fn new(
        path: impl Into<String>,
        service_name: impl Into<String>,
        service_port: i32,
    ) -> Self {
        Self {
            path: path.into(),
            path_type: PathType::Prefix,
            service_name: service_name.into(),
            service_port,
        }
    }

    /// Sets the path type.
    pub fn with_path_type(mut self, path_type: PathType) -> Self {
        self.path_type = path_type;
        self
    }
}

/// Ingress rule defining routing for a specific host.
#[derive(Debug, Clone)]
pub struct IngressRule {
    /// Hostname for this rule (optional, empty means all hosts).
    pub host: Option<String>,
    /// HTTP paths for this rule.
    pub paths: Vec<IngressPath>,
}

impl IngressRule {
    /// Creates a new Ingress rule.
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::ingress::{IngressRule, IngressPath};
    ///
    /// let rule = IngressRule::new()
    ///     .with_host("example.com")
    ///     .with_path(IngressPath::new("/", "web-service", 80));
    /// ```
    pub fn new() -> Self {
        Self {
            host: None,
            paths: Vec::new(),
        }
    }

    /// Sets the host for this rule.
    pub fn with_host(mut self, host: impl Into<String>) -> Self {
        self.host = Some(host.into());
        self
    }

    /// Adds a path to this rule.
    pub fn with_path(mut self, path: IngressPath) -> Self {
        self.paths.push(path);
        self
    }
}

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

/// Specification for creating a Kubernetes Ingress.
///
/// Ingress resources manage external access to services in a cluster,
/// typically HTTP/HTTPS traffic.
///
/// # Examples
///
/// ```no_run
/// use lmrc_kubernetes::ingress::{IngressSpec, IngressRule, IngressPath, IngressTLS};
///
/// let ingress = IngressSpec::new("my-ingress")
///     .with_ingress_class("nginx")
///     .with_rule(
///         IngressRule::new()
///             .with_host("example.com")
///             .with_path(IngressPath::new("/", "web-service", 80))
///     )
///     .with_tls(
///         IngressTLS::new("tls-secret")
///             .with_host("example.com")
///     );
/// ```
#[derive(Debug, Clone)]
pub struct IngressSpec {
    name: String,
    ingress_class: Option<String>,
    rules: Vec<IngressRule>,
    tls: Vec<IngressTLS>,
    labels: HashMap<String, String>,
    annotations: HashMap<String, String>,
}

impl IngressSpec {
    /// Creates a new IngressSpec with the given name.
    ///
    /// # Arguments
    ///
    /// * `name` - The name of the Ingress
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::ingress::IngressSpec;
    ///
    /// let ingress = IngressSpec::new("my-ingress");
    /// ```
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            ingress_class: None,
            rules: Vec::new(),
            tls: Vec::new(),
            labels: HashMap::new(),
            annotations: HashMap::new(),
        }
    }

    /// Sets the Ingress class name.
    ///
    /// # Arguments
    ///
    /// * `class_name` - The Ingress class (e.g., "nginx", "traefik")
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::ingress::IngressSpec;
    ///
    /// let ingress = IngressSpec::new("my-ingress")
    ///     .with_ingress_class("nginx");
    /// ```
    pub fn with_ingress_class(mut self, class_name: impl Into<String>) -> Self {
        self.ingress_class = Some(class_name.into());
        self
    }

    /// Adds an Ingress rule.
    ///
    /// # Arguments
    ///
    /// * `rule` - The IngressRule to add
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::ingress::{IngressSpec, IngressRule, IngressPath};
    ///
    /// let ingress = IngressSpec::new("my-ingress")
    ///     .with_rule(
    ///         IngressRule::new()
    ///             .with_host("api.example.com")
    ///             .with_path(IngressPath::new("/v1", "api-service", 8080))
    ///     );
    /// ```
    pub fn with_rule(mut self, rule: IngressRule) -> Self {
        self.rules.push(rule);
        self
    }

    /// Adds TLS configuration.
    ///
    /// # Arguments
    ///
    /// * `tls` - The TLS configuration
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::ingress::{IngressSpec, IngressTLS};
    ///
    /// let ingress = IngressSpec::new("my-ingress")
    ///     .with_tls(
    ///         IngressTLS::new("tls-secret")
    ///             .with_host("example.com")
    ///     );
    /// ```
    pub fn with_tls(mut self, tls: IngressTLS) -> Self {
        self.tls.push(tls);
        self
    }

    /// Adds a label to the Ingress.
    ///
    /// # Arguments
    ///
    /// * `key` - The label key
    /// * `value` - The label value
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::ingress::IngressSpec;
    ///
    /// let ingress = IngressSpec::new("my-ingress")
    ///     .with_label("app", "web");
    /// ```
    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 annotation to the Ingress.
    ///
    /// Annotations are often used to configure Ingress controller behavior.
    ///
    /// # Arguments
    ///
    /// * `key` - The annotation key
    /// * `value` - The annotation value
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::ingress::IngressSpec;
    ///
    /// let ingress = IngressSpec::new("my-ingress")
    ///     .with_annotation("nginx.ingress.kubernetes.io/rewrite-target", "/");
    /// ```
    pub fn with_annotation(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.annotations.insert(key.into(), value.into());
        self
    }

    /// Validates the Ingress specification.
    ///
    /// # Returns
    ///
    /// Returns `Ok(())` if valid, or an error message if invalid.
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::ingress::{IngressSpec, IngressRule, IngressPath};
    ///
    /// let ingress = IngressSpec::new("my-ingress")
    ///     .with_rule(
    ///         IngressRule::new()
    ///             .with_path(IngressPath::new("/", "service", 80))
    ///     );
    ///
    /// assert!(ingress.validate().is_ok());
    /// ```
    pub fn validate(&self) -> Result<(), String> {
        if self.name.is_empty() {
            return Err("Ingress name cannot be empty".to_string());
        }

        if self.rules.is_empty() {
            return Err("Ingress must have at least one rule".to_string());
        }

        for rule in &self.rules {
            if rule.paths.is_empty() {
                return Err("Ingress rule must have at least one path".to_string());
            }

            for path in &rule.paths {
                if path.service_name.is_empty() {
                    return Err("Ingress path must specify a service name".to_string());
                }
                if path.service_port <= 0 {
                    return Err("Ingress path must specify a valid service port".to_string());
                }
            }
        }

        Ok(())
    }

    // Getters

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

    /// Returns the Ingress class name.
    pub fn ingress_class(&self) -> Option<&str> {
        self.ingress_class.as_deref()
    }

    /// Returns the Ingress rules.
    pub fn rules(&self) -> &[IngressRule] {
        &self.rules
    }

    /// Returns the TLS configurations.
    pub fn tls_configs(&self) -> &[IngressTLS] {
        &self.tls
    }

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

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

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

    #[test]
    fn test_new_ingress() {
        let ingress = IngressSpec::new("test-ingress");
        assert_eq!(ingress.name(), "test-ingress");
        assert!(ingress.rules().is_empty());
    }

    #[test]
    fn test_with_rule() {
        let rule = IngressRule::new()
            .with_host("example.com")
            .with_path(IngressPath::new("/", "web", 80));

        let ingress = IngressSpec::new("test-ingress").with_rule(rule);

        assert_eq!(ingress.rules().len(), 1);
        assert_eq!(ingress.rules()[0].host.as_ref().unwrap(), "example.com");
    }

    #[test]
    fn test_with_tls() {
        let tls = IngressTLS::new("tls-secret").with_host("example.com");

        let ingress = IngressSpec::new("test-ingress").with_tls(tls);

        assert_eq!(ingress.tls_configs().len(), 1);
        assert_eq!(ingress.tls_configs()[0].secret_name, "tls-secret");
    }

    #[test]
    fn test_validation_no_rules() {
        let ingress = IngressSpec::new("test");
        assert!(ingress.validate().is_err());
    }

    #[test]
    fn test_validation_valid() {
        let rule = IngressRule::new().with_path(IngressPath::new("/", "service", 80));

        let ingress = IngressSpec::new("test").with_rule(rule);

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