1use crate::types::{ActionType, LogicalOperator, Operator, Value};
2use chrono::{DateTime, Utc};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone)]
7pub struct Condition {
8    pub field: String,
10    pub operator: Operator,
12    pub value: Value,
14}
15
16impl Condition {
17    pub fn new(field: String, operator: Operator, value: Value) -> Self {
19        Self {
20            field,
21            operator,
22            value,
23        }
24    }
25
26    pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
28        if let Some(field_value) = get_nested_value(facts, &self.field) {
29            self.operator.evaluate(field_value, &self.value)
31        } else {
32            false
33        }
34    }
35}
36
37#[derive(Debug, Clone)]
39pub enum ConditionGroup {
40    Single(Condition),
42    Compound {
44        left: Box<ConditionGroup>,
46        operator: LogicalOperator,
48        right: Box<ConditionGroup>,
50    },
51    Not(Box<ConditionGroup>),
53    Exists(Box<ConditionGroup>),
55    Forall(Box<ConditionGroup>),
57}
58
59impl ConditionGroup {
60    pub fn single(condition: Condition) -> Self {
62        ConditionGroup::Single(condition)
63    }
64
65    pub fn and(left: ConditionGroup, right: ConditionGroup) -> Self {
67        ConditionGroup::Compound {
68            left: Box::new(left),
69            operator: LogicalOperator::And,
70            right: Box::new(right),
71        }
72    }
73
74    pub fn or(left: ConditionGroup, right: ConditionGroup) -> Self {
76        ConditionGroup::Compound {
77            left: Box::new(left),
78            operator: LogicalOperator::Or,
79            right: Box::new(right),
80        }
81    }
82
83    #[allow(clippy::should_implement_trait)]
85    pub fn not(condition: ConditionGroup) -> Self {
86        ConditionGroup::Not(Box::new(condition))
87    }
88
89    pub fn exists(condition: ConditionGroup) -> Self {
91        ConditionGroup::Exists(Box::new(condition))
92    }
93
94    pub fn forall(condition: ConditionGroup) -> Self {
96        ConditionGroup::Forall(Box::new(condition))
97    }
98
99    pub fn evaluate(&self, facts: &HashMap<String, Value>) -> bool {
101        match self {
102            ConditionGroup::Single(condition) => condition.evaluate(facts),
103            ConditionGroup::Compound {
104                left,
105                operator,
106                right,
107            } => {
108                let left_result = left.evaluate(facts);
109                let right_result = right.evaluate(facts);
110                match operator {
111                    LogicalOperator::And => left_result && right_result,
112                    LogicalOperator::Or => left_result || right_result,
113                    LogicalOperator::Not => !left_result, }
115            }
116            ConditionGroup::Not(condition) => !condition.evaluate(facts),
117            ConditionGroup::Exists(_) | ConditionGroup::Forall(_) => {
118                false
121            }
122        }
123    }
124
125    pub fn evaluate_with_facts(&self, facts: &crate::engine::facts::Facts) -> bool {
127        use crate::engine::pattern_matcher::PatternMatcher;
128
129        match self {
130            ConditionGroup::Single(condition) => {
131                let fact_map = facts.get_all_facts();
132                condition.evaluate(&fact_map)
133            }
134            ConditionGroup::Compound {
135                left,
136                operator,
137                right,
138            } => {
139                let left_result = left.evaluate_with_facts(facts);
140                let right_result = right.evaluate_with_facts(facts);
141                match operator {
142                    LogicalOperator::And => left_result && right_result,
143                    LogicalOperator::Or => left_result || right_result,
144                    LogicalOperator::Not => !left_result,
145                }
146            }
147            ConditionGroup::Not(condition) => !condition.evaluate_with_facts(facts),
148            ConditionGroup::Exists(condition) => PatternMatcher::evaluate_exists(condition, facts),
149            ConditionGroup::Forall(condition) => PatternMatcher::evaluate_forall(condition, facts),
150        }
151    }
152}
153
154#[derive(Debug, Clone)]
156pub struct Rule {
157    pub name: String,
159    pub description: Option<String>,
161    pub salience: i32,
163    pub enabled: bool,
165    pub no_loop: bool,
167    pub lock_on_active: bool,
169    pub agenda_group: Option<String>,
171    pub activation_group: Option<String>,
173    pub date_effective: Option<DateTime<Utc>>,
175    pub date_expires: Option<DateTime<Utc>>,
177    pub conditions: ConditionGroup,
179    pub actions: Vec<ActionType>,
181}
182
183impl Rule {
184    pub fn new(name: String, conditions: ConditionGroup, actions: Vec<ActionType>) -> Self {
186        Self {
187            name,
188            description: None,
189            salience: 0,
190            enabled: true,
191            no_loop: false,
192            lock_on_active: false,
193            agenda_group: None,
194            activation_group: None,
195            date_effective: None,
196            date_expires: None,
197            conditions,
198            actions,
199        }
200    }
201
202    pub fn with_description(mut self, description: String) -> Self {
204        self.description = Some(description);
205        self
206    }
207
208    pub fn with_salience(mut self, salience: i32) -> Self {
210        self.salience = salience;
211        self
212    }
213
214    pub fn with_priority(mut self, priority: i32) -> Self {
216        self.salience = priority;
217        self
218    }
219
220    pub fn with_no_loop(mut self, no_loop: bool) -> Self {
222        self.no_loop = no_loop;
223        self
224    }
225
226    pub fn with_lock_on_active(mut self, lock_on_active: bool) -> Self {
228        self.lock_on_active = lock_on_active;
229        self
230    }
231
232    pub fn with_agenda_group(mut self, agenda_group: String) -> Self {
234        self.agenda_group = Some(agenda_group);
235        self
236    }
237
238    pub fn with_activation_group(mut self, activation_group: String) -> Self {
240        self.activation_group = Some(activation_group);
241        self
242    }
243
244    pub fn with_date_effective(mut self, date_effective: DateTime<Utc>) -> Self {
246        self.date_effective = Some(date_effective);
247        self
248    }
249
250    pub fn with_date_expires(mut self, date_expires: DateTime<Utc>) -> Self {
252        self.date_expires = Some(date_expires);
253        self
254    }
255
256    pub fn with_date_effective_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
258        let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
259        self.date_effective = Some(date);
260        Ok(self)
261    }
262
263    pub fn with_date_expires_str(mut self, date_str: &str) -> Result<Self, chrono::ParseError> {
265        let date = DateTime::parse_from_rfc3339(date_str)?.with_timezone(&Utc);
266        self.date_expires = Some(date);
267        Ok(self)
268    }
269
270    pub fn is_active_at(&self, timestamp: DateTime<Utc>) -> bool {
272        if let Some(effective) = self.date_effective {
274            if timestamp < effective {
275                return false;
276            }
277        }
278
279        if let Some(expires) = self.date_expires {
281            if timestamp >= expires {
282                return false;
283            }
284        }
285
286        true
287    }
288
289    pub fn is_active(&self) -> bool {
291        self.is_active_at(Utc::now())
292    }
293
294    pub fn matches(&self, facts: &HashMap<String, Value>) -> bool {
296        self.enabled && self.conditions.evaluate(facts)
297    }
298}
299
300#[derive(Debug, Clone)]
302pub struct RuleExecutionResult {
303    pub rule_name: String,
305    pub matched: bool,
307    pub actions_executed: Vec<String>,
309    pub execution_time_ms: f64,
311}
312
313impl RuleExecutionResult {
314    pub fn new(rule_name: String) -> Self {
316        Self {
317            rule_name,
318            matched: false,
319            actions_executed: Vec::new(),
320            execution_time_ms: 0.0,
321        }
322    }
323
324    pub fn matched(mut self) -> Self {
326        self.matched = true;
327        self
328    }
329
330    pub fn with_actions(mut self, actions: Vec<String>) -> Self {
332        self.actions_executed = actions;
333        self
334    }
335
336    pub fn with_execution_time(mut self, time_ms: f64) -> Self {
338        self.execution_time_ms = time_ms;
339        self
340    }
341}
342
343fn get_nested_value<'a>(data: &'a HashMap<String, Value>, path: &str) -> Option<&'a Value> {
345    let parts: Vec<&str> = path.split('.').collect();
346    let mut current = data.get(parts[0])?;
347
348    for part in parts.iter().skip(1) {
349        match current {
350            Value::Object(obj) => {
351                current = obj.get(*part)?;
352            }
353            _ => return None,
354        }
355    }
356
357    Some(current)
358}