//! Condition types for ABAC (Attribute-Based Access Control)
//!
//! Conditions allow fine-grained access control based on attributes of
//! the principal, resource, and request context.

use serde::{Deserialize, Serialize};

/// A condition that must be satisfied for a permission to apply
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Condition {
    /// The condition expression
    pub expression: ConditionExpr,
}

impl Condition {
    /// Create a new condition
    pub fn new(expression: ConditionExpr) -> Self {
        Self { expression }
    }

    /// Create a string equals condition
    pub fn string_equals(key: impl Into<String>, value: impl Into<String>) -> Self {
        Self::new(ConditionExpr::StringEquals {
            key: key.into(),
            value: value.into(),
        })
    }

    /// Create a string not equals condition
    pub fn string_not_equals(key: impl Into<String>, value: impl Into<String>) -> Self {
        Self::new(ConditionExpr::StringNotEquals {
            key: key.into(),
            value: value.into(),
        })
    }

    /// Create a string like condition (glob pattern)
    pub fn string_like(key: impl Into<String>, pattern: impl Into<String>) -> Self {
        Self::new(ConditionExpr::StringLike {
            key: key.into(),
            pattern: pattern.into(),
        })
    }

    /// Create an IP address condition
    pub fn ip_address(key: impl Into<String>, cidr: impl Into<String>) -> Self {
        Self::new(ConditionExpr::IpAddress {
            key: key.into(),
            cidr: cidr.into(),
        })
    }

    /// Create a time between condition
    pub fn time_between(start: impl Into<String>, end: impl Into<String>) -> Self {
        Self::new(ConditionExpr::TimeBetween {
            start: start.into(),
            end: end.into(),
        })
    }

    /// Create an AND condition
    pub fn and(conditions: Vec<Condition>) -> Self {
        Self::new(ConditionExpr::And(
            conditions.into_iter().map(|c| c.expression).collect(),
        ))
    }

    /// Create an OR condition
    pub fn or(conditions: Vec<Condition>) -> Self {
        Self::new(ConditionExpr::Or(
            conditions.into_iter().map(|c| c.expression).collect(),
        ))
    }

    /// Create a NOT condition
    #[allow(clippy::should_implement_trait)]
    pub fn not(condition: Condition) -> Self {
        Self::new(ConditionExpr::Not(Box::new(condition.expression)))
    }
}

/// Condition expression types
///
/// Note: Manual Serialize/Deserialize implementation to avoid deep recursion
/// during macro expansion (which would require recursion_limit = 2048).
#[derive(Debug, Clone, PartialEq)]
pub enum ConditionExpr {
    /// String equality check
    StringEquals {
        /// Key to check (e.g., "resource.owner", "principal.id")
        key: String,
        /// Expected value (can use ${variable} syntax)
        value: String,
    },

    /// String inequality check
    StringNotEquals { key: String, value: String },

    /// String pattern match (glob-style with * and ?)
    StringLike { key: String, pattern: String },

    /// String not matching pattern
    StringNotLike { key: String, pattern: String },

    /// Numeric equality
    NumericEquals { key: String, value: i64 },

    /// Numeric less than
    NumericLessThan { key: String, value: i64 },

    /// Numeric less than or equal
    NumericLessThanEquals { key: String, value: i64 },

    /// Numeric greater than
    NumericGreaterThan { key: String, value: i64 },

    /// Numeric greater than or equal
    NumericGreaterThanEquals { key: String, value: i64 },

    /// IP address in CIDR range
    IpAddress {
        /// Key to check (typically "request.source_ip")
        key: String,
        /// CIDR notation (e.g., "10.0.0.0/8")
        cidr: String,
    },

    /// IP address not in CIDR range
    NotIpAddress { key: String, cidr: String },

    /// Time-based condition
    TimeBetween {
        /// Start time (ISO 8601 format or HH:MM)
        start: String,
        /// End time (ISO 8601 format or HH:MM)
        end: String,
    },

    /// Check if a key exists and is not null
    Exists { key: String },

    /// Check if value is in a list
    StringEqualsAny { key: String, values: Vec<String> },

    /// Boolean value check
    Bool { key: String, value: bool },

    /// Logical AND of multiple conditions
    And(Vec<ConditionExpr>),

    /// Logical OR of multiple conditions
    Or(Vec<ConditionExpr>),

    /// Logical NOT of a condition
    Not(Box<ConditionExpr>),
}

impl ConditionExpr {
    /// Check if this expression uses variable substitution
    pub fn uses_variables(&self) -> bool {
        match self {
            ConditionExpr::StringEquals { value, .. }
            | ConditionExpr::StringNotEquals { value, .. } => value.contains("${"),
            ConditionExpr::StringLike { pattern, .. }
            | ConditionExpr::StringNotLike { pattern, .. } => pattern.contains("${"),
            ConditionExpr::And(exprs) | ConditionExpr::Or(exprs) => {
                exprs.iter().any(|e| e.uses_variables())
            }
            ConditionExpr::Not(expr) => expr.uses_variables(),
            _ => false,
        }
    }
}

// Manual Serialize implementation for ConditionExpr
// This avoids the deep trait bound recursion that occurs with derive macros
// on internally-tagged recursive enums.
impl Serialize for ConditionExpr {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        use serde::ser::SerializeMap;

        match self {
            ConditionExpr::StringEquals { key, value } => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("type", "string_equals")?;
                map.serialize_entry("key", key)?;
                map.serialize_entry("value", value)?;
                map.end()
            }
            ConditionExpr::StringNotEquals { key, value } => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("type", "string_not_equals")?;
                map.serialize_entry("key", key)?;
                map.serialize_entry("value", value)?;
                map.end()
            }
            ConditionExpr::StringLike { key, pattern } => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("type", "string_like")?;
                map.serialize_entry("key", key)?;
                map.serialize_entry("pattern", pattern)?;
                map.end()
            }
            ConditionExpr::StringNotLike { key, pattern } => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("type", "string_not_like")?;
                map.serialize_entry("key", key)?;
                map.serialize_entry("pattern", pattern)?;
                map.end()
            }
            ConditionExpr::NumericEquals { key, value } => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("type", "numeric_equals")?;
                map.serialize_entry("key", key)?;
                map.serialize_entry("value", value)?;
                map.end()
            }
            ConditionExpr::NumericLessThan { key, value } => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("type", "numeric_less_than")?;
                map.serialize_entry("key", key)?;
                map.serialize_entry("value", value)?;
                map.end()
            }
            ConditionExpr::NumericLessThanEquals { key, value } => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("type", "numeric_less_than_equals")?;
                map.serialize_entry("key", key)?;
                map.serialize_entry("value", value)?;
                map.end()
            }
            ConditionExpr::NumericGreaterThan { key, value } => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("type", "numeric_greater_than")?;
                map.serialize_entry("key", key)?;
                map.serialize_entry("value", value)?;
                map.end()
            }
            ConditionExpr::NumericGreaterThanEquals { key, value } => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("type", "numeric_greater_than_equals")?;
                map.serialize_entry("key", key)?;
                map.serialize_entry("value", value)?;
                map.end()
            }
            ConditionExpr::IpAddress { key, cidr } => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("type", "ip_address")?;
                map.serialize_entry("key", key)?;
                map.serialize_entry("cidr", cidr)?;
                map.end()
            }
            ConditionExpr::NotIpAddress { key, cidr } => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("type", "not_ip_address")?;
                map.serialize_entry("key", key)?;
                map.serialize_entry("cidr", cidr)?;
                map.end()
            }
            ConditionExpr::TimeBetween { start, end } => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("type", "time_between")?;
                map.serialize_entry("start", start)?;
                map.serialize_entry("end", end)?;
                map.end()
            }
            ConditionExpr::Exists { key } => {
                let mut map = serializer.serialize_map(Some(2))?;
                map.serialize_entry("type", "exists")?;
                map.serialize_entry("key", key)?;
                map.end()
            }
            ConditionExpr::StringEqualsAny { key, values } => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("type", "string_equals_any")?;
                map.serialize_entry("key", key)?;
                map.serialize_entry("values", values)?;
                map.end()
            }
            ConditionExpr::Bool { key, value } => {
                let mut map = serializer.serialize_map(Some(3))?;
                map.serialize_entry("type", "bool")?;
                map.serialize_entry("key", key)?;
                map.serialize_entry("value", value)?;
                map.end()
            }
            ConditionExpr::And(exprs) => {
                let mut map = serializer.serialize_map(Some(2))?;
                map.serialize_entry("type", "and")?;
                map.serialize_entry("conditions", exprs)?;
                map.end()
            }
            ConditionExpr::Or(exprs) => {
                let mut map = serializer.serialize_map(Some(2))?;
                map.serialize_entry("type", "or")?;
                map.serialize_entry("conditions", exprs)?;
                map.end()
            }
            ConditionExpr::Not(expr) => {
                let mut map = serializer.serialize_map(Some(2))?;
                map.serialize_entry("type", "not")?;
                map.serialize_entry("condition", expr)?;
                map.end()
            }
        }
    }
}

// Manual Deserialize implementation for ConditionExpr
impl<'de> Deserialize<'de> for ConditionExpr {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        use serde::de::{Error, MapAccess, Visitor};
        use std::fmt;

        struct ConditionExprVisitor;

        impl<'de> Visitor<'de> for ConditionExprVisitor {
            type Value = ConditionExpr;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a condition expression object with a 'type' field")
            }

            fn visit_map<M>(self, mut map: M) -> Result<ConditionExpr, M::Error>
            where
                M: MapAccess<'de>,
            {
                let mut expr_type: Option<String> = None;
                let mut key: Option<String> = None;
                let mut value_str: Option<String> = None;
                let mut value_i64: Option<i64> = None;
                let mut value_bool: Option<bool> = None;
                let mut pattern: Option<String> = None;
                let mut cidr: Option<String> = None;
                let mut start: Option<String> = None;
                let mut end: Option<String> = None;
                let mut values: Option<Vec<String>> = None;
                let mut conditions: Option<Vec<ConditionExpr>> = None;
                let mut condition: Option<Box<ConditionExpr>> = None;

                while let Some(field) = map.next_key::<String>()? {
                    match field.as_str() {
                        "type" => expr_type = Some(map.next_value()?),
                        "key" => key = Some(map.next_value()?),
                        "value" => {
                            // Value can be string, i64, or bool depending on type
                            // We'll try to deserialize as each
                            let raw: serde_json::Value = map.next_value()?;
                            match &raw {
                                serde_json::Value::String(s) => value_str = Some(s.clone()),
                                serde_json::Value::Number(n) => {
                                    value_i64 = n.as_i64();
                                }
                                serde_json::Value::Bool(b) => value_bool = Some(*b),
                                _ => {}
                            }
                        }
                        "pattern" => pattern = Some(map.next_value()?),
                        "cidr" => cidr = Some(map.next_value()?),
                        "start" => start = Some(map.next_value()?),
                        "end" => end = Some(map.next_value()?),
                        "values" => values = Some(map.next_value()?),
                        "conditions" => conditions = Some(map.next_value()?),
                        "condition" => condition = Some(map.next_value()?),
                        _ => {
                            // Skip unknown fields
                            let _: serde_json::Value = map.next_value()?;
                        }
                    }
                }

                let expr_type = expr_type.ok_or_else(|| Error::missing_field("type"))?;

                match expr_type.as_str() {
                    "string_equals" => Ok(ConditionExpr::StringEquals {
                        key: key.ok_or_else(|| Error::missing_field("key"))?,
                        value: value_str.ok_or_else(|| Error::missing_field("value"))?,
                    }),
                    "string_not_equals" => Ok(ConditionExpr::StringNotEquals {
                        key: key.ok_or_else(|| Error::missing_field("key"))?,
                        value: value_str.ok_or_else(|| Error::missing_field("value"))?,
                    }),
                    "string_like" => Ok(ConditionExpr::StringLike {
                        key: key.ok_or_else(|| Error::missing_field("key"))?,
                        pattern: pattern.ok_or_else(|| Error::missing_field("pattern"))?,
                    }),
                    "string_not_like" => Ok(ConditionExpr::StringNotLike {
                        key: key.ok_or_else(|| Error::missing_field("key"))?,
                        pattern: pattern.ok_or_else(|| Error::missing_field("pattern"))?,
                    }),
                    "numeric_equals" => Ok(ConditionExpr::NumericEquals {
                        key: key.ok_or_else(|| Error::missing_field("key"))?,
                        value: value_i64.ok_or_else(|| Error::missing_field("value"))?,
                    }),
                    "numeric_less_than" => Ok(ConditionExpr::NumericLessThan {
                        key: key.ok_or_else(|| Error::missing_field("key"))?,
                        value: value_i64.ok_or_else(|| Error::missing_field("value"))?,
                    }),
                    "numeric_less_than_equals" => Ok(ConditionExpr::NumericLessThanEquals {
                        key: key.ok_or_else(|| Error::missing_field("key"))?,
                        value: value_i64.ok_or_else(|| Error::missing_field("value"))?,
                    }),
                    "numeric_greater_than" => Ok(ConditionExpr::NumericGreaterThan {
                        key: key.ok_or_else(|| Error::missing_field("key"))?,
                        value: value_i64.ok_or_else(|| Error::missing_field("value"))?,
                    }),
                    "numeric_greater_than_equals" => Ok(ConditionExpr::NumericGreaterThanEquals {
                        key: key.ok_or_else(|| Error::missing_field("key"))?,
                        value: value_i64.ok_or_else(|| Error::missing_field("value"))?,
                    }),
                    "ip_address" => Ok(ConditionExpr::IpAddress {
                        key: key.ok_or_else(|| Error::missing_field("key"))?,
                        cidr: cidr.ok_or_else(|| Error::missing_field("cidr"))?,
                    }),
                    "not_ip_address" => Ok(ConditionExpr::NotIpAddress {
                        key: key.ok_or_else(|| Error::missing_field("key"))?,
                        cidr: cidr.ok_or_else(|| Error::missing_field("cidr"))?,
                    }),
                    "time_between" => Ok(ConditionExpr::TimeBetween {
                        start: start.ok_or_else(|| Error::missing_field("start"))?,
                        end: end.ok_or_else(|| Error::missing_field("end"))?,
                    }),
                    "exists" => Ok(ConditionExpr::Exists {
                        key: key.ok_or_else(|| Error::missing_field("key"))?,
                    }),
                    "string_equals_any" => Ok(ConditionExpr::StringEqualsAny {
                        key: key.ok_or_else(|| Error::missing_field("key"))?,
                        values: values.ok_or_else(|| Error::missing_field("values"))?,
                    }),
                    "bool" => Ok(ConditionExpr::Bool {
                        key: key.ok_or_else(|| Error::missing_field("key"))?,
                        value: value_bool.ok_or_else(|| Error::missing_field("value"))?,
                    }),
                    "and" => Ok(ConditionExpr::And(
                        conditions.ok_or_else(|| Error::missing_field("conditions"))?,
                    )),
                    "or" => Ok(ConditionExpr::Or(
                        conditions.ok_or_else(|| Error::missing_field("conditions"))?,
                    )),
                    "not" => Ok(ConditionExpr::Not(
                        condition.ok_or_else(|| Error::missing_field("condition"))?,
                    )),
                    other => Err(Error::unknown_variant(
                        other,
                        &[
                            "string_equals",
                            "string_not_equals",
                            "string_like",
                            "string_not_like",
                            "numeric_equals",
                            "numeric_less_than",
                            "numeric_less_than_equals",
                            "numeric_greater_than",
                            "numeric_greater_than_equals",
                            "ip_address",
                            "not_ip_address",
                            "time_between",
                            "exists",
                            "string_equals_any",
                            "bool",
                            "and",
                            "or",
                            "not",
                        ],
                    )),
                }
            }
        }

        deserializer.deserialize_map(ConditionExprVisitor)
    }
}

/// Context keys that can be used in conditions
pub mod keys {
    /// Principal-related keys
    pub mod principal {
        pub const ID: &str = "principal.id";
        pub const KIND: &str = "principal.kind";
        pub const ORG_ID: &str = "principal.org_id";
        pub const PROJECT_ID: &str = "principal.project_id";
        pub const NODE_ID: &str = "principal.node_id";
    }

    /// Resource-related keys
    pub mod resource {
        pub const KIND: &str = "resource.kind";
        pub const ID: &str = "resource.id";
        pub const PROJECT_ID: &str = "resource.project_id";
        pub const OWNER: &str = "resource.owner";
        pub const NODE: &str = "resource.node";
        pub const REGION: &str = "resource.region";
    }

    /// Request-related keys
    pub mod request {
        pub const SOURCE_IP: &str = "request.source_ip";
        pub const TIME: &str = "request.time";
        pub const AUTH_LEVEL: &str = "request.auth_level";
    }
}

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

    #[test]
    fn test_condition_serialization() {
        let cond = Condition::string_equals("resource.owner", "${principal.id}");
        let json = serde_json::to_string(&cond).unwrap();
        assert!(json.contains("string_equals"));

        let parsed: Condition = serde_json::from_str(&json).unwrap();
        assert_eq!(cond, parsed);
    }

    #[test]
    fn test_condition_and() {
        let cond = Condition::and(vec![
            Condition::string_equals("resource.owner", "${principal.id}"),
            Condition::ip_address("request.source_ip", "10.0.0.0/8"),
        ]);

        if let ConditionExpr::And(exprs) = &cond.expression {
            assert_eq!(exprs.len(), 2);
        } else {
            panic!("expected And expression");
        }
    }

    #[test]
    fn test_uses_variables() {
        let cond = Condition::string_equals("resource.owner", "${principal.id}");
        assert!(cond.expression.uses_variables());

        let cond = Condition::string_equals("resource.owner", "fixed-value");
        assert!(!cond.expression.uses_variables());
    }

    #[test]
    fn test_nested_condition_serialization() {
        let cond = Condition::and(vec![
            Condition::string_equals("a", "b"),
            Condition::or(vec![
                Condition::ip_address("ip", "10.0.0.0/8"),
                Condition::not(Condition::string_like("key", "pattern*")),
            ]),
        ]);

        let json = serde_json::to_string_pretty(&cond).unwrap();
        let parsed: Condition = serde_json::from_str(&json).unwrap();
        assert_eq!(cond, parsed);
    }

    #[test]
    fn test_numeric_condition_serialization() {
        let cond = Condition::new(ConditionExpr::NumericGreaterThan {
            key: "count".to_string(),
            value: 100,
        });
        let json = serde_json::to_string(&cond).unwrap();
        let parsed: Condition = serde_json::from_str(&json).unwrap();
        assert_eq!(cond, parsed);
    }

    #[test]
    fn test_bool_condition_serialization() {
        let cond = Condition::new(ConditionExpr::Bool {
            key: "is_admin".to_string(),
            value: true,
        });
        let json = serde_json::to_string(&cond).unwrap();
        let parsed: Condition = serde_json::from_str(&json).unwrap();
        assert_eq!(cond, parsed);
    }
}
