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

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

    if let Some(e) = last_err {
        ConditionEvaluationResult::Unknown { reason: e }
    } else {
        ConditionEvaluationResult::Fail
    }
}

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

    // Helper to create a minimal, valid ToolCondition instance that the closure can ignore.
    fn dummy_condition() -> ToolCondition {
        ToolCondition::Boolean(BooleanCombinationToolCondition {
            op: BooleanOperator::AnyOf,
            conditions: vec![],
        })
    }

    #[test]
    fn returns_fail_for_empty_conditions() {
        let conditions: Vec<ToolCondition> = vec![];
        let result = any_condition_matches(&conditions, |_c| ConditionEvaluationResult::Fail);
        assert!(matches!(result, ConditionEvaluationResult::Fail));
    }

    #[test]
    fn returns_fail_when_all_fail() {
        let conditions = vec![dummy_condition(), dummy_condition(), dummy_condition()];
        let result = any_condition_matches(&conditions, |_c| ConditionEvaluationResult::Fail);
        assert!(matches!(result, ConditionEvaluationResult::Fail));
    }

    #[test]
    fn returns_pass_when_any_passes() {
        let conditions = vec![dummy_condition(), dummy_condition(), dummy_condition()];
        let mut idx = 0usize;
        let result = any_condition_matches(&conditions, |_c| {
            idx += 1;
            if idx == 2 {
                ConditionEvaluationResult::Pass
            } else {
                ConditionEvaluationResult::Fail
            }
        });
        assert!(matches!(result, ConditionEvaluationResult::Pass));
    }

    #[test]
    fn returns_unknown_when_none_pass_and_at_least_one_unknown() {
        let conditions = vec![dummy_condition(), dummy_condition()];
        let mut idx = 0usize;
        let result = any_condition_matches(&conditions, |_c| {
            idx += 1;
            if idx == 1 {
                ConditionEvaluationResult::Unknown {
                    reason: PolicyEngineError::MissingExpectedValueAtPath {
                        source_tag: SourceTag::State.to_string(),
                        path: "user.id".to_string(),
                    },
                }
            } else {
                ConditionEvaluationResult::Fail
            }
        });
        assert!(matches!(result, ConditionEvaluationResult::Unknown { .. }));
    }

    #[test]
    fn returns_pass_priority_over_unknown() {
        let conditions = vec![dummy_condition(), dummy_condition(), dummy_condition()];
        let mut idx = 0usize;
        let result = any_condition_matches(&conditions, |_c| {
            idx += 1;
            match idx {
                1 => ConditionEvaluationResult::Unknown {
                    reason: PolicyEngineError::MissingActualValueAtPath {
                        source_tag: SourceTag::Args.to_string(),
                        path: "amount".to_string(),
                    },
                },
                2 => ConditionEvaluationResult::Pass,
                _ => ConditionEvaluationResult::Fail,
            }
        });
        assert!(matches!(result, ConditionEvaluationResult::Pass));
    }

    #[test]
    fn short_circuits_on_first_pass() {
        let conditions = vec![dummy_condition(), dummy_condition(), dummy_condition()];
        let mut calls = 0usize;
        let _ = any_condition_matches(&conditions, |_c| {
            calls += 1;
            if calls == 2 {
                ConditionEvaluationResult::Pass
            } else {
                ConditionEvaluationResult::Fail
            }
        });
        // Should have only called the predicate twice (stop after first Pass)
        assert_eq!(calls, 2);
    }
}
