use crate::policy_types::{ConditionEvaluationResult, ToolCondition};

pub fn all_conditions_match<F>(conditions: &[ToolCondition], mut f: F) -> ConditionEvaluationResult
where
    F: FnMut(&ToolCondition) -> ConditionEvaluationResult,
{
    for item in conditions {
        match f(item) {
            ConditionEvaluationResult::Pass => continue,
            ConditionEvaluationResult::Fail => return ConditionEvaluationResult::Fail,
            ConditionEvaluationResult::Unknown { reason } => {
                return ConditionEvaluationResult::Unknown { reason };
            }
        }
    }

    ConditionEvaluationResult::Pass
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::errors::PolicyEngineError;
    use crate::policy_types::{BooleanCombinationToolCondition, BooleanOperator, SourceTag};

    fn dummy_condition() -> ToolCondition {
        ToolCondition::Boolean(BooleanCombinationToolCondition {
            op: BooleanOperator::AllOf,
            conditions: vec![],
        })
    }

    #[test]
    fn returns_pass_for_empty_conditions() {
        let conditions: Vec<ToolCondition> = vec![];
        let res = all_conditions_match(&conditions, |_c| ConditionEvaluationResult::Pass);
        assert!(matches!(res, ConditionEvaluationResult::Pass));
    }

    #[test]
    fn returns_pass_when_all_pass() {
        let conditions = vec![dummy_condition(), dummy_condition(), dummy_condition()];
        let res = all_conditions_match(&conditions, |_c| ConditionEvaluationResult::Pass);
        assert!(matches!(res, ConditionEvaluationResult::Pass));
    }

    #[test]
    fn returns_fail_and_short_circuits_on_first_fail() {
        let conditions = vec![dummy_condition(), dummy_condition(), dummy_condition()];
        let mut calls = 0usize;
        let res = all_conditions_match(&conditions, |_c| {
            calls += 1;
            if calls == 2 {
                ConditionEvaluationResult::Fail
            } else {
                ConditionEvaluationResult::Pass
            }
        });
        assert!(matches!(res, ConditionEvaluationResult::Fail));
        // Should stop evaluating after the first Fail (2 calls total)
        assert_eq!(calls, 2);
    }

    #[test]
    fn returns_unknown_and_short_circuits_on_first_unknown() {
        let conditions = vec![dummy_condition(), dummy_condition(), dummy_condition()];
        let mut calls = 0usize;
        let res = all_conditions_match(&conditions, |_c| {
            calls += 1;
            if calls == 2 {
                ConditionEvaluationResult::Unknown {
                    reason: PolicyEngineError::MissingActualValueAtPath {
                        source_tag: SourceTag::State.to_string(),
                        path: "x".into(),
                    },
                }
            } else {
                ConditionEvaluationResult::Pass
            }
        });
        assert!(matches!(res, ConditionEvaluationResult::Unknown { .. }));
        // Should stop evaluating after the first Unknown (2 calls total)
        assert_eq!(calls, 2);
    }

    #[test]
    fn fail_takes_effect_if_it_occurs_before_unknown() {
        let conditions = vec![dummy_condition(), dummy_condition(), dummy_condition()];
        let mut calls = 0usize;
        let res = all_conditions_match(&conditions, |_c| {
            calls += 1;
            match calls {
                1 => ConditionEvaluationResult::Fail,
                2 => ConditionEvaluationResult::Unknown {
                    reason: PolicyEngineError::MissingExpectedValueAtPath {
                        source_tag: SourceTag::Args.to_string(),
                        path: "y".into(),
                    },
                },
                _ => ConditionEvaluationResult::Pass,
            }
        });
        assert!(matches!(res, ConditionEvaluationResult::Fail));
        // Should stop at the first (failing) condition
        assert_eq!(calls, 1);
    }

    #[test]
    fn unknown_takes_effect_if_it_occurs_before_fail() {
        let conditions = vec![dummy_condition(), dummy_condition(), dummy_condition()];
        let mut calls = 0usize;
        let res = all_conditions_match(&conditions, |_c| {
            calls += 1;
            match calls {
                1 => ConditionEvaluationResult::Unknown {
                    reason: PolicyEngineError::MissingExpectedValueAtPath {
                        source_tag: SourceTag::State.to_string(),
                        path: "a".into(),
                    },
                },
                2 => ConditionEvaluationResult::Fail,
                _ => ConditionEvaluationResult::Pass,
            }
        });
        assert!(matches!(res, ConditionEvaluationResult::Unknown { .. }));
        // Should stop at the first (unknown) condition
        assert_eq!(calls, 1);
    }
}
