//! Gateway API specification builders.

use std::collections::HashMap;

/// Protocol types for Gateway listeners.
#[derive(Debug, Clone, PartialEq)]
pub enum Protocol {
    /// HTTP protocol.
    HTTP,
    /// HTTPS protocol.
    HTTPS,
    /// TCP protocol.
    TCP,
    /// TLS protocol.
    TLS,
}

impl Protocol {
    /// Converts Protocol to string representation.
    pub fn as_str(&self) -> &str {
        match self {
            Protocol::HTTP => "HTTP",
            Protocol::HTTPS => "HTTPS",
            Protocol::TCP => "TCP",
            Protocol::TLS => "TLS",
        }
    }
}

/// Gateway listener configuration.
#[derive(Debug, Clone)]
pub struct GatewayListener {
    /// Name of the listener.
    pub name: String,
    /// Port number.
    pub port: i32,
    /// Protocol.
    pub protocol: Protocol,
    /// Hostname (optional).
    pub hostname: Option<String>,
}

impl GatewayListener {
    /// Creates a new Gateway listener.
    ///
    /// # Arguments
    ///
    /// * `name` - Listener name
    /// * `port` - Port number
    /// * `protocol` - Protocol type
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::gateway::{GatewayListener, Protocol};
    ///
    /// let listener = GatewayListener::new("http", 80, Protocol::HTTP);
    /// ```
    pub fn new(name: impl Into<String>, port: i32, protocol: Protocol) -> Self {
        Self {
            name: name.into(),
            port,
            protocol,
            hostname: None,
        }
    }

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

/// Specification for creating a Kubernetes Gateway.
///
/// Gateway represents an instance of a service-traffic handling infrastructure
/// (e.g., a load balancer).
///
/// # Examples
///
/// ```no_run
/// use lmrc_kubernetes::gateway::{GatewaySpec, GatewayListener, Protocol};
///
/// let gateway = GatewaySpec::new("my-gateway")
///     .with_gateway_class("istio")
///     .with_listener(GatewayListener::new("http", 80, Protocol::HTTP))
///     .with_listener(GatewayListener::new("https", 443, Protocol::HTTPS));
/// ```
#[derive(Debug, Clone)]
pub struct GatewaySpec {
    name: String,
    gateway_class: String,
    listeners: Vec<GatewayListener>,
    labels: HashMap<String, String>,
}

impl GatewaySpec {
    /// Creates a new GatewaySpec with the given name.
    ///
    /// # Arguments
    ///
    /// * `name` - The name of the Gateway
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::gateway::GatewaySpec;
    ///
    /// let gateway = GatewaySpec::new("my-gateway");
    /// ```
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            gateway_class: "istio".to_string(), // Default to istio
            listeners: Vec::new(),
            labels: HashMap::new(),
        }
    }

    /// Sets the GatewayClass name.
    ///
    /// # Arguments
    ///
    /// * `class_name` - The GatewayClass (e.g., "istio", "nginx")
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::gateway::GatewaySpec;
    ///
    /// let gateway = GatewaySpec::new("my-gateway")
    ///     .with_gateway_class("nginx");
    /// ```
    pub fn with_gateway_class(mut self, class_name: impl Into<String>) -> Self {
        self.gateway_class = class_name.into();
        self
    }

    /// Adds a listener to the Gateway.
    ///
    /// # Arguments
    ///
    /// * `listener` - The listener configuration
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::gateway::{GatewaySpec, GatewayListener, Protocol};
    ///
    /// let gateway = GatewaySpec::new("my-gateway")
    ///     .with_listener(GatewayListener::new("http", 80, Protocol::HTTP));
    /// ```
    pub fn with_listener(mut self, listener: GatewayListener) -> Self {
        self.listeners.push(listener);
        self
    }

    /// Adds a label to the Gateway.
    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 Gateway 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("Gateway name cannot be empty".to_string());
        }

        if self.gateway_class.is_empty() {
            return Err("GatewayClass cannot be empty".to_string());
        }

        if self.listeners.is_empty() {
            return Err("Gateway must have at least one listener".to_string());
        }

        for listener in &self.listeners {
            if listener.port <= 0 || listener.port > 65535 {
                return Err(format!(
                    "Invalid port {} for listener '{}'",
                    listener.port, listener.name
                ));
            }
        }

        Ok(())
    }

    // Getters

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

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

    /// Returns the listeners.
    pub fn listeners(&self) -> &[GatewayListener] {
        &self.listeners
    }

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

/// HTTP route match configuration.
#[derive(Debug, Clone)]
pub struct HTTPRouteMatch {
    /// Path to match.
    pub path: Option<String>,
    /// Path match type (Exact, Prefix, RegularExpression).
    pub path_type: Option<String>,
}

impl HTTPRouteMatch {
    /// Creates a new HTTP route match with prefix path matching.
    pub fn prefix(path: impl Into<String>) -> Self {
        Self {
            path: Some(path.into()),
            path_type: Some("PathPrefix".to_string()),
        }
    }

    /// Creates a new HTTP route match with exact path matching.
    pub fn exact(path: impl Into<String>) -> Self {
        Self {
            path: Some(path.into()),
            path_type: Some("Exact".to_string()),
        }
    }
}

/// HTTP route backend reference.
#[derive(Debug, Clone)]
pub struct HTTPRouteBackend {
    /// Service name.
    pub name: String,
    /// Service port.
    pub port: i32,
    /// Weight for traffic splitting (optional).
    pub weight: Option<i32>,
}

impl HTTPRouteBackend {
    /// Creates a new backend reference.
    pub fn new(name: impl Into<String>, port: i32) -> Self {
        Self {
            name: name.into(),
            port,
            weight: None,
        }
    }

    /// Sets the weight for traffic splitting.
    pub fn with_weight(mut self, weight: i32) -> Self {
        self.weight = Some(weight);
        self
    }
}

/// HTTP route rule configuration.
#[derive(Debug, Clone)]
pub struct HTTPRouteRule {
    /// Matches for this rule.
    pub matches: Vec<HTTPRouteMatch>,
    /// Backend references.
    pub backends: Vec<HTTPRouteBackend>,
}

impl HTTPRouteRule {
    /// Creates a new HTTP route rule.
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_kubernetes::gateway::{HTTPRouteRule, HTTPRouteMatch, HTTPRouteBackend};
    ///
    /// let rule = HTTPRouteRule::new()
    ///     .with_match(HTTPRouteMatch::prefix("/api"))
    ///     .with_backend(HTTPRouteBackend::new("api-service", 8080));
    /// ```
    pub fn new() -> Self {
        Self {
            matches: Vec::new(),
            backends: Vec::new(),
        }
    }

    /// Adds a match to this rule.
    pub fn with_match(mut self, match_config: HTTPRouteMatch) -> Self {
        self.matches.push(match_config);
        self
    }

    /// Adds a backend to this rule.
    pub fn with_backend(mut self, backend: HTTPRouteBackend) -> Self {
        self.backends.push(backend);
        self
    }
}

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

/// Specification for creating a Kubernetes HTTPRoute.
///
/// HTTPRoute defines HTTP traffic routing rules.
///
/// # Examples
///
/// ```no_run
/// use lmrc_kubernetes::gateway::{HTTPRoute, HTTPRouteRule, HTTPRouteMatch, HTTPRouteBackend};
///
/// let route = HTTPRoute::new("my-route")
///     .with_gateway("my-gateway")
///     .with_hostname("api.example.com")
///     .with_rule(
///         HTTPRouteRule::new()
///             .with_match(HTTPRouteMatch::prefix("/v1"))
///             .with_backend(HTTPRouteBackend::new("api-v1", 8080))
///     );
/// ```
#[derive(Debug, Clone)]
pub struct HTTPRoute {
    name: String,
    gateway_name: Option<String>,
    hostnames: Vec<String>,
    rules: Vec<HTTPRouteRule>,
    labels: HashMap<String, String>,
}

impl HTTPRoute {
    /// Creates a new HTTPRoute with the given name.
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            gateway_name: None,
            hostnames: Vec::new(),
            rules: Vec::new(),
            labels: HashMap::new(),
        }
    }

    /// Sets the Gateway this route attaches to.
    pub fn with_gateway(mut self, gateway_name: impl Into<String>) -> Self {
        self.gateway_name = Some(gateway_name.into());
        self
    }

    /// Adds a hostname to this route.
    pub fn with_hostname(mut self, hostname: impl Into<String>) -> Self {
        self.hostnames.push(hostname.into());
        self
    }

    /// Adds a rule to this route.
    pub fn with_rule(mut self, rule: HTTPRouteRule) -> Self {
        self.rules.push(rule);
        self
    }

    /// Adds a label to the HTTPRoute.
    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 HTTPRoute specification.
    pub fn validate(&self) -> Result<(), String> {
        if self.name.is_empty() {
            return Err("HTTPRoute name cannot be empty".to_string());
        }

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

        for rule in &self.rules {
            if rule.backends.is_empty() {
                return Err("HTTPRoute rule must have at least one backend".to_string());
            }
        }

        Ok(())
    }

    // Getters

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

    /// Returns the Gateway name this route attaches to.
    pub fn gateway_name(&self) -> Option<&str> {
        self.gateway_name.as_deref()
    }

    /// Returns the hostnames.
    pub fn hostnames(&self) -> &[String] {
        &self.hostnames
    }

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

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

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

    #[test]
    fn test_new_gateway() {
        let gateway = GatewaySpec::new("test-gateway");
        assert_eq!(gateway.name(), "test-gateway");
    }

    #[test]
    fn test_gateway_with_listener() {
        let gateway = GatewaySpec::new("test-gateway").with_listener(GatewayListener::new(
            "http",
            80,
            Protocol::HTTP,
        ));

        assert_eq!(gateway.listeners().len(), 1);
        assert_eq!(gateway.listeners()[0].port, 80);
    }

    #[test]
    fn test_http_route() {
        let route = HTTPRoute::new("test-route")
            .with_gateway("my-gateway")
            .with_hostname("example.com")
            .with_rule(
                HTTPRouteRule::new()
                    .with_match(HTTPRouteMatch::prefix("/api"))
                    .with_backend(HTTPRouteBackend::new("api-service", 8080)),
            );

        assert_eq!(route.name(), "test-route");
        assert_eq!(route.gateway_name(), Some("my-gateway"));
        assert_eq!(route.rules().len(), 1);
    }

    #[test]
    fn test_gateway_validation() {
        let gateway = GatewaySpec::new("test");
        assert!(gateway.validate().is_err()); // No listeners

        let gateway = GatewaySpec::new("test").with_listener(GatewayListener::new(
            "http",
            80,
            Protocol::HTTP,
        ));
        assert!(gateway.validate().is_ok());
    }
}
