//! Input validation utilities for Hetzner Cloud resources.
//!
//! This module provides validators for common input types like IP addresses,
//! CIDR ranges, port numbers, and naming conventions.

use crate::{HetznerError, Result};
use regex::Regex;
use std::sync::OnceLock;

/// Maximum length for resource names in Hetzner Cloud.
pub const MAX_NAME_LENGTH: usize = 63;

/// Minimum length for resource names in Hetzner Cloud.
pub const MIN_NAME_LENGTH: usize = 1;

/// Valid port range.
pub const MIN_PORT: u16 = 1;
pub const MAX_PORT: u16 = 65535;

/// Validates a resource name according to Hetzner Cloud naming rules.
///
/// Names must:
/// - Be 1-63 characters long
/// - Contain only lowercase letters, numbers, hyphens, and underscores
/// - Start with a letter or number
/// - End with a letter or number
///
/// # Examples
///
/// ```
/// use lmrc_hetzner::validation::validate_name;
///
/// assert!(validate_name("my-server", "server").is_ok());
/// assert!(validate_name("web_server_01", "server").is_ok());
/// assert!(validate_name("", "server").is_err());
/// assert!(validate_name("-invalid", "server").is_err());
/// ```
pub fn validate_name(name: &str, resource_type: &str) -> Result<()> {
    static NAME_REGEX: OnceLock<Regex> = OnceLock::new();
    let regex =
        NAME_REGEX.get_or_init(|| Regex::new(r"^[a-z0-9][a-z0-9_-]*[a-z0-9]$|^[a-z0-9]$").unwrap());

    if name.is_empty() {
        return Err(HetznerError::InvalidParameter {
            parameter: format!("{}_name", resource_type),
            reason: "Name cannot be empty".to_string(),
        });
    }

    if name.len() > MAX_NAME_LENGTH {
        return Err(HetznerError::InvalidParameter {
            parameter: format!("{}_name", resource_type),
            reason: format!(
                "Name exceeds maximum length of {} characters",
                MAX_NAME_LENGTH
            ),
        });
    }

    if !regex.is_match(name) {
        return Err(HetznerError::InvalidParameter {
            parameter: format!("{}_name", resource_type),
            reason: "Name must contain only lowercase letters, numbers, hyphens, and underscores, and must start and end with a letter or number".to_string(),
        });
    }

    Ok(())
}

/// Validates an IPv4 address in CIDR notation.
///
/// # Examples
///
/// ```
/// use lmrc_hetzner::validation::validate_ipv4_cidr;
///
/// assert!(validate_ipv4_cidr("10.0.0.0/16", "network").is_ok());
/// assert!(validate_ipv4_cidr("192.168.1.0/24", "network").is_ok());
/// assert!(validate_ipv4_cidr("10.0.0.0", "network").is_err());
/// assert!(validate_ipv4_cidr("10.0.0.0/33", "network").is_err());
/// ```
pub fn validate_ipv4_cidr(cidr: &str, parameter_name: &str) -> Result<()> {
    static CIDR_REGEX: OnceLock<Regex> = OnceLock::new();
    let regex = CIDR_REGEX.get_or_init(|| {
        Regex::new(r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})$").unwrap()
    });

    let caps = regex
        .captures(cidr)
        .ok_or_else(|| HetznerError::InvalidParameter {
            parameter: parameter_name.to_string(),
            reason: "Must be in CIDR notation (e.g., 10.0.0.0/16)".to_string(),
        })?;

    // Validate each octet
    for i in 1..=4 {
        let _octet: u8 = caps[i]
            .parse()
            .map_err(|_| HetznerError::InvalidParameter {
                parameter: parameter_name.to_string(),
                reason: format!("Invalid IP address octet: {}", &caps[i]),
            })?;
        // Note: u8 parsing already ensures 0-255 range
    }

    // Validate prefix length
    let prefix_len: u8 = caps[5]
        .parse()
        .map_err(|_| HetznerError::InvalidParameter {
            parameter: parameter_name.to_string(),
            reason: format!("Invalid prefix length: {}", &caps[5]),
        })?;

    if prefix_len > 32 {
        return Err(HetznerError::InvalidParameter {
            parameter: parameter_name.to_string(),
            reason: format!("Prefix length must be 0-32, got {}", prefix_len),
        });
    }

    Ok(())
}

/// Validates an IPv4 address (without CIDR notation).
///
/// # Examples
///
/// ```
/// use lmrc_hetzner::validation::validate_ipv4;
///
/// assert!(validate_ipv4("192.168.1.1", "source_ip").is_ok());
/// assert!(validate_ipv4("10.0.0.1", "source_ip").is_ok());
/// assert!(validate_ipv4("256.1.1.1", "source_ip").is_err());
/// assert!(validate_ipv4("192.168.1", "source_ip").is_err());
/// ```
pub fn validate_ipv4(ip: &str, parameter_name: &str) -> Result<()> {
    static IP_REGEX: OnceLock<Regex> = OnceLock::new();
    let regex = IP_REGEX
        .get_or_init(|| Regex::new(r"^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$").unwrap());

    let caps = regex
        .captures(ip)
        .ok_or_else(|| HetznerError::InvalidParameter {
            parameter: parameter_name.to_string(),
            reason: "Must be a valid IPv4 address (e.g., 192.168.1.1)".to_string(),
        })?;

    // Validate each octet
    for i in 1..=4 {
        let _octet: u8 = caps[i]
            .parse()
            .map_err(|_| HetznerError::InvalidParameter {
                parameter: parameter_name.to_string(),
                reason: format!("Invalid IP address octet: {}", &caps[i]),
            })?;
        // Note: u8 parsing already ensures 0-255 range
    }

    Ok(())
}

/// Validates a port number.
///
/// # Examples
///
/// ```
/// use lmrc_hetzner::validation::validate_port;
///
/// assert!(validate_port(80, "listen_port").is_ok());
/// assert!(validate_port(443, "listen_port").is_ok());
/// assert!(validate_port(65535, "listen_port").is_ok());
/// assert!(validate_port(0, "listen_port").is_err());
/// ```
pub fn validate_port(port: u16, parameter_name: &str) -> Result<()> {
    if port < MIN_PORT {
        return Err(HetznerError::InvalidParameter {
            parameter: parameter_name.to_string(),
            reason: format!(
                "Port must be between {} and {}, got {}",
                MIN_PORT, MAX_PORT, port
            ),
        });
    }
    Ok(())
}

/// Validates a port range string (e.g., "80", "8000-8999").
///
/// # Examples
///
/// ```
/// use lmrc_hetzner::validation::validate_port_range;
///
/// assert!(validate_port_range("80", "port").is_ok());
/// assert!(validate_port_range("8000-8999", "port").is_ok());
/// assert!(validate_port_range("any", "port").is_ok());
/// assert!(validate_port_range("invalid", "port").is_err());
/// assert!(validate_port_range("9000-8000", "port").is_err()); // Start > end
/// ```
pub fn validate_port_range(port_range: &str, parameter_name: &str) -> Result<()> {
    if port_range == "any" {
        return Ok(());
    }

    static PORT_RANGE_REGEX: OnceLock<Regex> = OnceLock::new();
    let regex = PORT_RANGE_REGEX.get_or_init(|| Regex::new(r"^(\d+)(?:-(\d+))?$").unwrap());

    let caps = regex
        .captures(port_range)
        .ok_or_else(|| HetznerError::InvalidParameter {
            parameter: parameter_name.to_string(),
            reason: "Port range must be a number (e.g., '80'), range (e.g., '8000-8999'), or 'any'"
                .to_string(),
        })?;

    let start: u16 = caps[1]
        .parse()
        .map_err(|_| HetznerError::InvalidParameter {
            parameter: parameter_name.to_string(),
            reason: format!("Invalid port number: {}", &caps[1]),
        })?;

    validate_port(start, parameter_name)?;

    if let Some(end_match) = caps.get(2) {
        let end: u16 = end_match
            .as_str()
            .parse()
            .map_err(|_| HetznerError::InvalidParameter {
                parameter: parameter_name.to_string(),
                reason: format!("Invalid port number: {}", end_match.as_str()),
            })?;

        validate_port(end, parameter_name)?;

        if start > end {
            return Err(HetznerError::InvalidParameter {
                parameter: parameter_name.to_string(),
                reason: format!(
                    "Port range start ({}) must be less than or equal to end ({})",
                    start, end
                ),
            });
        }
    }

    Ok(())
}

/// Validates a firewall protocol.
///
/// # Examples
///
/// ```
/// use lmrc_hetzner::validation::validate_protocol;
///
/// assert!(validate_protocol("tcp", "protocol").is_ok());
/// assert!(validate_protocol("udp", "protocol").is_ok());
/// assert!(validate_protocol("icmp", "protocol").is_ok());
/// assert!(validate_protocol("invalid", "protocol").is_err());
/// ```
pub fn validate_protocol(protocol: &str, parameter_name: &str) -> Result<()> {
    match protocol {
        "tcp" | "udp" | "icmp" | "esp" | "gre" => Ok(()),
        _ => Err(HetznerError::InvalidParameter {
            parameter: parameter_name.to_string(),
            reason: format!(
                "Protocol must be one of: tcp, udp, icmp, esp, gre. Got: {}",
                protocol
            ),
        }),
    }
}

/// Validates a firewall direction.
///
/// # Examples
///
/// ```
/// use lmrc_hetzner::validation::validate_direction;
///
/// assert!(validate_direction("in", "direction").is_ok());
/// assert!(validate_direction("out", "direction").is_ok());
/// assert!(validate_direction("invalid", "direction").is_err());
/// ```
pub fn validate_direction(direction: &str, parameter_name: &str) -> Result<()> {
    match direction {
        "in" | "out" => Ok(()),
        _ => Err(HetznerError::InvalidParameter {
            parameter: parameter_name.to_string(),
            reason: format!("Direction must be 'in' or 'out'. Got: {}", direction),
        }),
    }
}

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

    #[test]
    fn test_validate_name() {
        assert!(validate_name("my-server", "server").is_ok());
        assert!(validate_name("web_server_01", "server").is_ok());
        assert!(validate_name("a", "server").is_ok());
        assert!(validate_name("a1", "server").is_ok());

        assert!(validate_name("", "server").is_err());
        assert!(validate_name("-invalid", "server").is_err());
        assert!(validate_name("invalid-", "server").is_err());
        assert!(validate_name("_invalid", "server").is_err());
        assert!(validate_name("Invalid", "server").is_err());
        assert!(validate_name(&"a".repeat(64), "server").is_err());
    }

    #[test]
    fn test_validate_ipv4_cidr() {
        assert!(validate_ipv4_cidr("10.0.0.0/16", "ip_range").is_ok());
        assert!(validate_ipv4_cidr("192.168.1.0/24", "ip_range").is_ok());
        assert!(validate_ipv4_cidr("0.0.0.0/0", "ip_range").is_ok());

        assert!(validate_ipv4_cidr("10.0.0.0", "ip_range").is_err());
        assert!(validate_ipv4_cidr("10.0.0.0/33", "ip_range").is_err());
        assert!(validate_ipv4_cidr("256.0.0.0/16", "ip_range").is_err());
        assert!(validate_ipv4_cidr("invalid", "ip_range").is_err());
    }

    #[test]
    fn test_validate_ipv4() {
        assert!(validate_ipv4("192.168.1.1", "ip").is_ok());
        assert!(validate_ipv4("10.0.0.1", "ip").is_ok());
        assert!(validate_ipv4("0.0.0.0", "ip").is_ok());

        assert!(validate_ipv4("256.1.1.1", "ip").is_err());
        assert!(validate_ipv4("192.168.1", "ip").is_err());
        assert!(validate_ipv4("192.168.1.1.1", "ip").is_err());
    }

    #[test]
    fn test_validate_port() {
        assert!(validate_port(80, "port").is_ok());
        assert!(validate_port(443, "port").is_ok());
        assert!(validate_port(65535, "port").is_ok());
        assert!(validate_port(1, "port").is_ok());

        assert!(validate_port(0, "port").is_err());
    }

    #[test]
    fn test_validate_port_range() {
        assert!(validate_port_range("80", "port").is_ok());
        assert!(validate_port_range("8000-8999", "port").is_ok());
        assert!(validate_port_range("any", "port").is_ok());

        assert!(validate_port_range("invalid", "port").is_err());
        assert!(validate_port_range("9000-8000", "port").is_err());
        assert!(validate_port_range("0-100", "port").is_err());
    }

    #[test]
    fn test_validate_protocol() {
        assert!(validate_protocol("tcp", "protocol").is_ok());
        assert!(validate_protocol("udp", "protocol").is_ok());
        assert!(validate_protocol("icmp", "protocol").is_ok());

        assert!(validate_protocol("invalid", "protocol").is_err());
        assert!(validate_protocol("TCP", "protocol").is_err());
    }

    #[test]
    fn test_validate_direction() {
        assert!(validate_direction("in", "direction").is_ok());
        assert!(validate_direction("out", "direction").is_ok());

        assert!(validate_direction("invalid", "direction").is_err());
        assert!(validate_direction("IN", "direction").is_err());
    }
}
