/// Decision Tree Engine
///
/// Manages conditional question flow based on user answers.
/// Evaluates expressions to determine which questions to ask next.
///
/// Features:
/// - Tree traversal with expression evaluation
/// - Conditional question display
/// - Dependency tracking
/// - Answer validation
use std::collections::HashMap;

use regex::Regex;

/// Expression evaluator for conditional logic
#[derive(Debug, Clone)]
pub struct Expression {
    expr: String,
}

impl Expression {
    pub fn new(expr: impl Into<String>) -> Self {
        Self { expr: expr.into() }
    }

    /// Evaluate expression against current answers
    ///
    /// Supports:
    /// - Simple equality: "field == 'value'"
    /// - Comparisons: "field >= 5"
    /// - Boolean logic: "a and b", "x or y"
    /// - Set containment: "field in ['a', 'b', 'c']"
    pub fn evaluate(&self, answers: &HashMap<String, String>) -> bool {
        let expr = self.expr.clone();

        // Handle simple equality comparisons: "key == 'value'"
        if let Some(caps) = Regex::new(r#"(\w+)\s*==\s*['"]([^'"]*)['"']"#)
            .ok()
            .and_then(|re| re.captures(&expr))
        {
            let key = caps.get(1).map(|m| m.as_str()).unwrap_or("");
            let expected = caps.get(2).map(|m| m.as_str()).unwrap_or("");
            if let Some(actual) = answers.get(key) {
                return actual == expected;
            }
            return false;
        }

        // Handle "in" operator: "key in ['a', 'b', 'c']"
        if let Some(caps) = Regex::new(r"(\w+)\s+in\s+\[\s*([^\]]*)\s*\]")
            .ok()
            .and_then(|re| re.captures(&expr))
        {
            let key = caps.get(1).map(|m| m.as_str()).unwrap_or("");
            let values_str = caps.get(2).map(|m| m.as_str()).unwrap_or("");

            if let Some(actual) = answers.get(key) {
                let values: Vec<&str> = values_str
                    .split(',')
                    .map(|s| s.trim().trim_matches('\'').trim_matches('"'))
                    .collect();
                return values.contains(&actual.as_str());
            }
            return false;
        }

        // Handle "and" operator
        if expr.contains(" and ") {
            let parts: Vec<&str> = expr.split(" and ").collect();
            return parts
                .iter()
                .all(|part| Expression::new(*part).evaluate(answers));
        }

        // Handle "or" operator
        if expr.contains(" or ") {
            let parts: Vec<&str> = expr.split(" or ").collect();
            return parts
                .iter()
                .any(|part| Expression::new(*part).evaluate(answers));
        }

        // Default: treat as true (no condition means always ask)
        true
    }
}

/// Single node in decision tree
#[derive(Debug, Clone)]
pub struct DecisionNode {
    pub question_id: String,
    pub next_nodes: HashMap<String, String>, // answer -> next question_id
    pub default_next: Option<String>,        // fallback if answer not in next_nodes
}

impl DecisionNode {
    pub fn new(question_id: impl Into<String>) -> Self {
        Self {
            question_id: question_id.into(),
            next_nodes: HashMap::new(),
            default_next: None,
        }
    }

    pub fn add_transition(
        mut self,
        answer: impl Into<String>,
        next_question: impl Into<String>,
    ) -> Self {
        self.next_nodes.insert(answer.into(), next_question.into());
        self
    }

    pub fn set_default(mut self, next_question: impl Into<String>) -> Self {
        self.default_next = Some(next_question.into());
        self
    }

    /// Get next question ID based on answer
    pub fn get_next(&self, answer: &str) -> Option<String> {
        self.next_nodes
            .get(answer)
            .cloned()
            .or_else(|| {
                // Try wildcard match (*) for "any answer" transitions
                self.next_nodes.get("*").cloned()
            })
            .or_else(|| self.default_next.clone())
    }
}

/// Complete decision tree for questionnaire
#[derive(Debug, Clone)]
pub struct DecisionTree {
    root: String,
    nodes: HashMap<String, DecisionNode>,
}

impl DecisionTree {
    pub fn new(root: impl Into<String>) -> Self {
        Self {
            root: root.into(),
            nodes: HashMap::new(),
        }
    }

    pub fn add_node(mut self, node: DecisionNode) -> Self {
        self.nodes.insert(node.question_id.clone(), node);
        self
    }

    /// Get root question ID
    pub fn get_root(&self) -> &str {
        &self.root
    }

    /// Get starting node
    pub fn get_start_node(&self) -> Option<&DecisionNode> {
        self.nodes.get(&self.root)
    }

    /// Get next question based on current answer
    pub fn get_next_question(&self, current_question: &str, answer: &str) -> Option<String> {
        self.nodes
            .get(current_question)
            .and_then(|node| node.get_next(answer))
    }

    /// Check if question exists in tree
    pub fn has_question(&self, question_id: &str) -> bool {
        self.nodes.contains_key(question_id)
    }

    /// Get all question IDs in tree
    pub fn all_question_ids(&self) -> Vec<&str> {
        self.nodes.keys().map(|k| k.as_str()).collect()
    }

    /// Check if answer leads to end of questionnaire
    pub fn is_terminal(&self, current_question: &str, answer: &str) -> bool {
        match self.get_next_question(current_question, answer) {
            None => true,
            Some(next_id) => next_id == "END" || !self.has_question(&next_id),
        }
    }
}

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

    #[test]
    fn test_expression_equality() {
        let expr = Expression::new("deployment_mode == 'prod'");
        let mut answers = HashMap::new();
        answers.insert("deployment_mode".to_string(), "prod".to_string());
        assert!(expr.evaluate(&answers));

        answers.insert("deployment_mode".to_string(), "dev".to_string());
        assert!(!expr.evaluate(&answers));
    }

    #[test]
    fn test_expression_in_operator() {
        let expr = Expression::new("env in ['prod', 'staging']");
        let mut answers = HashMap::new();
        answers.insert("env".to_string(), "prod".to_string());
        assert!(expr.evaluate(&answers));

        answers.insert("env".to_string(), "dev".to_string());
        assert!(!expr.evaluate(&answers));
    }

    #[test]
    fn test_expression_and_operator() {
        let expr = Expression::new("has_db == 'true' and deployment_mode == 'prod'");
        let mut answers = HashMap::new();
        answers.insert("has_db".to_string(), "true".to_string());
        answers.insert("deployment_mode".to_string(), "prod".to_string());
        assert!(expr.evaluate(&answers));

        answers.insert("has_db".to_string(), "false".to_string());
        assert!(!expr.evaluate(&answers));
    }

    #[test]
    fn test_decision_tree_traversal() {
        let root_node = DecisionNode::new("q1")
            .add_transition("yes", "q2")
            .add_transition("no", "END");

        let q2_node = DecisionNode::new("q2").add_transition("*", "q3");

        let tree = DecisionTree::new("q1")
            .add_node(root_node)
            .add_node(q2_node)
            .add_node(DecisionNode::new("q3"));

        assert_eq!(tree.get_next_question("q1", "yes"), Some("q2".to_string()));
        assert_eq!(tree.get_next_question("q1", "no"), Some("END".to_string()));
        assert_eq!(
            tree.get_next_question("q2", "anything"),
            Some("q3".to_string())
        );
    }

    #[test]
    fn test_terminal_question() {
        let q1_node = DecisionNode::new("q1")
            .add_transition("finish", "END")
            .set_default("q2");
        let q2_node = DecisionNode::new("q2");
        let tree = DecisionTree::new("q1").add_node(q1_node).add_node(q2_node);

        assert!(tree.is_terminal("q1", "finish")); // Maps to END
        assert!(!tree.is_terminal("q1", "continue")); // Maps to q2 via default
    }
}
