//! ACL Rule Translation for OVN
//!
//! This module translates SecurityGroupRule objects into OVN ACL match expressions.
//! It supports TCP, UDP, ICMP, and wildcard protocols with port ranges and CIDR matching.

use prismnet_types::{IpProtocol, RuleDirection, SecurityGroupRule};

/// Build OVN ACL match expression from a SecurityGroupRule
///
/// # Arguments
/// * `rule` - The security group rule to translate
/// * `port_name` - Optional logical port name to include in match (e.g., "port-123")
///
/// # Returns
/// A complete OVN match expression string
///
/// # Examples
/// ```
/// use prismnet_types::{SecurityGroupRule, SecurityGroupId, RuleDirection, IpProtocol};
/// use prismnet_server::ovn::acl::build_acl_match;
///
/// let mut rule = SecurityGroupRule::new(
///     SecurityGroupId::new(),
///     RuleDirection::Ingress,
///     IpProtocol::Tcp,
/// );
/// rule.port_range_min = Some(80);
/// rule.port_range_max = Some(80);
/// rule.remote_cidr = Some("10.0.0.0/8".to_string());
///
/// let match_expr = build_acl_match(&rule, Some("port-123"));
/// // Result: "inport == \"port-123\" && ip4 && tcp && tcp.dst == 80 && ip4.src == 10.0.0.0/8"
/// ```
pub fn build_acl_match(rule: &SecurityGroupRule, port_name: Option<&str>) -> String {
    let mut parts = Vec::new();

    // Add port constraint if provided
    if let Some(port) = port_name {
        match rule.direction {
            RuleDirection::Ingress => {
                parts.push(format!("inport == \"{}\"", port));
            }
            RuleDirection::Egress => {
                parts.push(format!("outport == \"{}\"", port));
            }
        }
    }

    // Add IP version (we only support IPv4 for now)
    parts.push("ip4".to_string());

    // Add protocol-specific matching
    match rule.protocol {
        IpProtocol::Tcp => {
            parts.push("tcp".to_string());
            if let Some(port_match) = build_port_match("tcp", rule) {
                parts.push(port_match);
            }
        }
        IpProtocol::Udp => {
            parts.push("udp".to_string());
            if let Some(port_match) = build_port_match("udp", rule) {
                parts.push(port_match);
            }
        }
        IpProtocol::Icmp => {
            parts.push("icmp4".to_string());
        }
        IpProtocol::Icmpv6 => {
            parts.push("icmp6".to_string());
        }
        IpProtocol::Any => {
            // No additional protocol constraint, just ip4
        }
    }

    // Add CIDR matching based on direction
    if let Some(cidr) = &rule.remote_cidr {
        let cidr_match = match rule.direction {
            RuleDirection::Ingress => format!("ip4.src == {}", cidr),
            RuleDirection::Egress => format!("ip4.dst == {}", cidr),
        };
        parts.push(cidr_match);
    }

    parts.join(" && ")
}

/// Build port matching expression for TCP/UDP
fn build_port_match(protocol: &str, rule: &SecurityGroupRule) -> Option<String> {
    match (rule.port_range_min, rule.port_range_max) {
        (Some(min), Some(max)) if min == max => {
            // Single port
            Some(format!("{}.dst == {}", protocol, min))
        }
        (Some(min), Some(max)) if min < max => {
            // Port range
            Some(format!(
                "{}.dst >= {} && {}.dst <= {}",
                protocol, min, protocol, max
            ))
        }
        (Some(min), None) => {
            // Only min specified, treat as single port
            Some(format!("{}.dst == {}", protocol, min))
        }
        (None, Some(max)) => {
            // Only max specified, treat as upper bound
            Some(format!("{}.dst <= {}", protocol, max))
        }
        (None, None) => {
            // No port constraint, match any port
            None
        }
        _ => None,
    }
}

/// Get OVN direction string from rule direction
///
/// OVN uses "to-lport" for ingress (traffic TO the port) and
/// "from-lport" for egress (traffic FROM the port)
pub fn rule_direction_to_ovn(direction: &RuleDirection) -> &'static str {
    match direction {
        RuleDirection::Ingress => "to-lport",
        RuleDirection::Egress => "from-lport",
    }
}

/// Calculate ACL priority based on rule specificity
///
/// More specific rules get higher priority:
/// - Protocol + CIDR + port range: 1000
/// - Protocol + CIDR or port: 800-900
/// - Protocol only: 700
/// - Any protocol: 600
pub fn calculate_priority(rule: &SecurityGroupRule) -> u16 {
    let mut priority = 600; // Base priority for "any" protocol

    let has_port = rule.port_range_min.is_some() || rule.port_range_max.is_some();
    let has_cidr = rule.remote_cidr.is_some();

    // Protocol specificity
    match rule.protocol {
        IpProtocol::Any => {}
        IpProtocol::Tcp | IpProtocol::Udp | IpProtocol::Icmp | IpProtocol::Icmpv6 => {
            priority += 100; // Protocol only: 700
        }
    }

    // Port and/or CIDR add specificity
    if has_port && has_cidr {
        // Both port and CIDR: most specific
        priority += 300; // Total: 1000
    } else if has_port || has_cidr {
        // Either port or CIDR
        priority += 100; // Total: 800
    }

    priority
}

#[cfg(test)]
mod tests {
    use super::*;
    use prismnet_types::{SecurityGroupId, SecurityGroupRule};

    #[test]
    fn test_tcp_single_port() {
        let mut rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Ingress,
            IpProtocol::Tcp,
        );
        rule.port_range_min = Some(80);
        rule.port_range_max = Some(80);

        let match_expr = build_acl_match(&rule, None);
        assert!(match_expr.contains("tcp"));
        assert!(match_expr.contains("tcp.dst == 80"));
        assert!(match_expr.contains("ip4"));
    }

    #[test]
    fn test_tcp_port_range() {
        let mut rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Ingress,
            IpProtocol::Tcp,
        );
        rule.port_range_min = Some(1024);
        rule.port_range_max = Some(65535);

        let match_expr = build_acl_match(&rule, None);
        assert!(match_expr.contains("tcp.dst >= 1024"));
        assert!(match_expr.contains("tcp.dst <= 65535"));
    }

    #[test]
    fn test_udp_single_port() {
        let mut rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Ingress,
            IpProtocol::Udp,
        );
        rule.port_range_min = Some(53);
        rule.port_range_max = Some(53);

        let match_expr = build_acl_match(&rule, None);
        assert!(match_expr.contains("udp"));
        assert!(match_expr.contains("udp.dst == 53"));
    }

    #[test]
    fn test_udp_port_range() {
        let mut rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Egress,
            IpProtocol::Udp,
        );
        rule.port_range_min = Some(5000);
        rule.port_range_max = Some(6000);

        let match_expr = build_acl_match(&rule, None);
        assert!(match_expr.contains("udp.dst >= 5000"));
        assert!(match_expr.contains("udp.dst <= 6000"));
    }

    #[test]
    fn test_icmp_protocol() {
        let rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Ingress,
            IpProtocol::Icmp,
        );

        let match_expr = build_acl_match(&rule, None);
        assert!(match_expr.contains("icmp4"));
        assert!(match_expr.contains("ip4"));
    }

    #[test]
    fn test_any_protocol() {
        let rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Ingress,
            IpProtocol::Any,
        );

        let match_expr = build_acl_match(&rule, None);
        assert!(match_expr.contains("ip4"));
        assert!(!match_expr.contains("tcp"));
        assert!(!match_expr.contains("udp"));
        assert!(!match_expr.contains("icmp"));
    }

    #[test]
    fn test_cidr_ingress() {
        let mut rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Ingress,
            IpProtocol::Any,
        );
        rule.remote_cidr = Some("10.0.0.0/8".to_string());

        let match_expr = build_acl_match(&rule, None);
        assert!(match_expr.contains("ip4.src == 10.0.0.0/8"));
    }

    #[test]
    fn test_cidr_egress() {
        let mut rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Egress,
            IpProtocol::Any,
        );
        rule.remote_cidr = Some("192.168.0.0/16".to_string());

        let match_expr = build_acl_match(&rule, None);
        assert!(match_expr.contains("ip4.dst == 192.168.0.0/16"));
    }

    #[test]
    fn test_complete_rule_with_port() {
        let mut rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Ingress,
            IpProtocol::Tcp,
        );
        rule.port_range_min = Some(443);
        rule.port_range_max = Some(443);
        rule.remote_cidr = Some("0.0.0.0/0".to_string());

        let match_expr = build_acl_match(&rule, Some("port-123"));
        assert!(match_expr.contains("inport == \"port-123\""));
        assert!(match_expr.contains("ip4"));
        assert!(match_expr.contains("tcp"));
        assert!(match_expr.contains("tcp.dst == 443"));
        assert!(match_expr.contains("ip4.src == 0.0.0.0/0"));
    }

    #[test]
    fn test_egress_with_port_name() {
        let mut rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Egress,
            IpProtocol::Tcp,
        );
        rule.port_range_min = Some(22);
        rule.port_range_max = Some(22);

        let match_expr = build_acl_match(&rule, Some("port-456"));
        assert!(match_expr.contains("outport == \"port-456\""));
    }

    #[test]
    fn test_direction_to_ovn() {
        assert_eq!(
            rule_direction_to_ovn(&RuleDirection::Ingress),
            "to-lport"
        );
        assert_eq!(
            rule_direction_to_ovn(&RuleDirection::Egress),
            "from-lport"
        );
    }

    #[test]
    fn test_priority_any_protocol() {
        let rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Ingress,
            IpProtocol::Any,
        );
        assert_eq!(calculate_priority(&rule), 600);
    }

    #[test]
    fn test_priority_with_protocol() {
        let rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Ingress,
            IpProtocol::Tcp,
        );
        assert_eq!(calculate_priority(&rule), 700);
    }

    #[test]
    fn test_priority_with_port() {
        let mut rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Ingress,
            IpProtocol::Tcp,
        );
        rule.port_range_min = Some(80);
        rule.port_range_max = Some(80);
        assert_eq!(calculate_priority(&rule), 800);
    }

    #[test]
    fn test_priority_with_cidr() {
        let mut rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Ingress,
            IpProtocol::Tcp,
        );
        rule.remote_cidr = Some("10.0.0.0/8".to_string());
        assert_eq!(calculate_priority(&rule), 800);
    }

    #[test]
    fn test_priority_full_specificity() {
        let mut rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Ingress,
            IpProtocol::Tcp,
        );
        rule.port_range_min = Some(443);
        rule.port_range_max = Some(443);
        rule.remote_cidr = Some("10.0.0.0/8".to_string());
        assert_eq!(calculate_priority(&rule), 1000);
    }

    #[test]
    fn test_port_only_min() {
        let mut rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Ingress,
            IpProtocol::Tcp,
        );
        rule.port_range_min = Some(8080);

        let match_expr = build_acl_match(&rule, None);
        assert!(match_expr.contains("tcp.dst == 8080"));
    }

    #[test]
    fn test_ssh_rule_example() {
        let mut rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Ingress,
            IpProtocol::Tcp,
        );
        rule.port_range_min = Some(22);
        rule.port_range_max = Some(22);
        rule.remote_cidr = Some("203.0.113.0/24".to_string());

        let match_expr = build_acl_match(&rule, None);
        assert!(match_expr.contains("tcp"));
        assert!(match_expr.contains("tcp.dst == 22"));
        assert!(match_expr.contains("ip4.src == 203.0.113.0/24"));
    }

    #[test]
    fn test_http_https_range() {
        let mut rule = SecurityGroupRule::new(
            SecurityGroupId::new(),
            RuleDirection::Ingress,
            IpProtocol::Tcp,
        );
        rule.port_range_min = Some(80);
        rule.port_range_max = Some(443);

        let match_expr = build_acl_match(&rule, None);
        assert!(match_expr.contains("tcp.dst >= 80"));
        assert!(match_expr.contains("tcp.dst <= 443"));
    }
}
