use kameleoon_trace_logging::trace_method;

use crate::{
    datafile::datafile::DataFile,
    logging::{KameleoonLogger, LogLevel},
    targeting::{TargetingData, condition_wrapper::ConditionWrapper, models::ConditionDataModel},
};

pub(crate) enum TargetingTreeNode {
    Cond(ConditionWrapper),
    Oper {
        or_operator: bool,
        left_child: Box<TargetingTreeNode>,
        right_child: Box<TargetingTreeNode>,
    },
}

impl TargetingTreeNode {
    fn first_level_from_json(model: ConditionDataModel) -> Option<Self> {
        Self::level_from_json(model.conditions.into_iter(), model.or_operators.into_iter(), |cond_list_model| {
            Self::level_from_json(
                cond_list_model.conditions.into_iter(),
                cond_list_model.or_operators.into_iter(),
                |cond_model| Some(TargetingTreeNode::Cond(ConditionWrapper::from_json(cond_model))),
            )
        })
    }

    fn level_from_json<T>(
        cond_iter: impl Iterator<Item = T>,
        mut or_op_iter: impl Iterator<Item = bool>,
        cond_to_node: impl Fn(T) -> Option<Self>,
    ) -> Option<Self> {
        let mut node: Option<Self> = None;
        for cond in cond_iter {
            if let Some(rnode) = cond_to_node(cond) {
                node = Some(match node {
                    Some(node) => {
                        TargetingTreeNode::Oper {
                            or_operator: or_op_iter.next().unwrap_or(false), //~ or maybe OR should be default operator?
                            left_child: Box::new(node),
                            right_child: Box::new(rnode),
                        }
                    }
                    None => rnode,
                })
            }
        }
        return node;
    }
}

#[derive(Default)]
pub(crate) struct TargetingTree {
    root: Option<TargetingTreeNode>,
}

impl TargetingTree {
    pub fn from_json(model: ConditionDataModel) -> Option<Self> {
        TargetingTreeNode::first_level_from_json(model).map_or_else(
            || {
                KameleoonLogger::error("Failed to parse targeting tree");
                None
            },
            |root| Some(TargetingTree { root: Some(root) }),
        )
    }

    #[trace_method(LogLevel::Debug, skip(self, data))]
    pub fn check(&self, data: &TargetingData<impl DataFile>) -> bool {
        return self.root.as_ref().map_or(true, |root| Self::check_node(root, data));
    }

    fn check_node(node: &TargetingTreeNode, data: &TargetingData<impl DataFile>) -> bool {
        match node {
            TargetingTreeNode::Cond(condition) => condition.check(data),
            TargetingTreeNode::Oper {
                or_operator,
                left_child,
                right_child,
            } => {
                let left_result = Self::check_node(left_child, data);
                if *or_operator {
                    left_result || Self::check_node(right_child, data)
                } else {
                    left_result && Self::check_node(right_child, data)
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::utils::test_utils::load_targeting_tree;

    fn iter_nodes(node: &TargetingTreeNode, f: &mut impl FnMut(&TargetingTreeNode)) {
        if let TargetingTreeNode::Oper {
            left_child,
            right_child,
            ..
        } = node
        {
            iter_nodes(&left_child, f);
            iter_nodes(&right_child, f);
        }
        f(node);
    }

    enum TreeNode {
        Cond { kind: &'static str, inverted: bool },
        Oper { or_operator: bool },
    }

    #[test]
    fn test_from_json() {
        let mut expected_nodes = vec![
            TreeNode::Cond {
                kind: "EXCLUSIVE_EXPERIMENT",
                inverted: false,
            },
            TreeNode::Cond {
                kind: "TARGET_EXPERIMENT",
                inverted: false,
            },
            TreeNode::Oper { or_operator: false },
            TreeNode::Cond {
                kind: "TARGET_PERSONALIZATION",
                inverted: true,
            },
            TreeNode::Oper { or_operator: true },
            TreeNode::Cond {
                kind: "OPERATING_SYSTEM",
                inverted: false,
            },
            TreeNode::Cond {
                kind: "?",
                inverted: false,
            },
            TreeNode::Oper { or_operator: true },
            TreeNode::Oper { or_operator: true },
        ]
        .into_iter();
        let tree = load_targeting_tree("resources/conditions/mock_segment_complex_tree.json");
        iter_nodes(tree.root.as_ref().unwrap(), &mut |node| {
            let expected_node = expected_nodes.next().unwrap();
            match expected_node {
                TreeNode::Cond { kind, inverted } => match node {
                    TargetingTreeNode::Cond(condition) => {
                        assert_eq!(kind, condition.condition().unwrap().kind());
                        assert_eq!(inverted, condition.inverted());
                    }
                    TargetingTreeNode::Oper { .. } => {
                        panic!("Expected Cond node, got Oper");
                    }
                },
                TreeNode::Oper { or_operator } => {
                    let expected_or_operator = or_operator;
                    match node {
                        TargetingTreeNode::Oper { or_operator, .. } => {
                            assert_eq!(expected_or_operator, *or_operator);
                        }
                        TargetingTreeNode::Cond(_) => {
                            panic!("Expected Oper node, got Cond");
                        }
                    }
                }
            }
        });
    }
}
