//! Formula expression evaluator.

use std::collections::HashMap;

use crate::mappings::formula::ast::{BinaryOperator, Expr, UnaryOperator};
use crate::mappings::formula::functions::get_function;
use crate::mappings::formula::resolver::ConceptResolver;
use crate::mappings::formula::FormulaError;
use crate::mappings::types::TargetField;

/// Context for formula evaluation.
pub struct FormulaContext<'a> {
    /// Resolved field values from XBRL and other formulas
    pub field_values: HashMap<TargetField, f64>,
    /// Stack to detect circular references
    pub evaluation_stack: Vec<TargetField>,
    /// Optional concept resolver for XBRL concept references
    pub concept_resolver: Option<&'a dyn ConceptResolver>,
}

impl<'a> std::fmt::Debug for FormulaContext<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("FormulaContext")
            .field("field_values", &self.field_values)
            .field("evaluation_stack", &self.evaluation_stack)
            .field("concept_resolver", &self.concept_resolver.is_some())
            .finish()
    }
}

impl<'a> FormulaContext<'a> {
    /// Create a new evaluation context.
    #[must_use]
    pub fn new(field_values: HashMap<TargetField, f64>) -> Self {
        Self {
            field_values,
            evaluation_stack: Vec::new(),
            concept_resolver: None,
        }
    }

    /// Create an empty context for tests.
    #[cfg(test)]
    pub fn empty() -> Self {
        Self {
            field_values: HashMap::new(),
            evaluation_stack: Vec::new(),
            concept_resolver: None,
        }
    }

    /// Set the concept resolver for this context.
    pub fn with_concept_resolver(mut self, resolver: &'a dyn ConceptResolver) -> Self {
        self.concept_resolver = Some(resolver);
        self
    }
}

/// Formula expression evaluator.
pub struct FormulaEvaluator<'a> {
    context: &'a FormulaContext<'a>,
}

impl<'a> FormulaEvaluator<'a> {
    /// Create a new evaluator with the given context.
    #[must_use]
    pub fn new(context: &'a FormulaContext<'a>) -> Self {
        Self { context }
    }

    /// Evaluate an expression to a numeric value.
    pub fn evaluate(&self, expr: &Expr) -> Result<f64, FormulaError> {
        match expr {
            Expr::Literal(n) => Ok(*n),

            Expr::FieldRef(field) => {
                // Check for circular reference
                if self.context.evaluation_stack.contains(field) {
                    return Err(FormulaError::CircularReference(format!(
                        "${}",
                        field.as_str()
                    )));
                }

                self.context
                    .field_values
                    .get(field)
                    .copied()
                    .ok_or_else(|| FormulaError::UnknownField(field.as_str().to_string()))
            }

            Expr::ConceptRef(concept_ref) => {
                let resolver =
                    self.context
                        .concept_resolver
                        .ok_or_else(|| FormulaError::Evaluation {
                            message: "Concept resolver not available".to_string(),
                        })?;

                resolver.resolve_concept(
                    &concept_ref.taxonomy,
                    &concept_ref.concept,
                    concept_ref.unit_family,
                )
            }

            Expr::BinaryOp { op, left, right } => {
                let l = self.evaluate(left)?;
                let r = self.evaluate(right)?;
                self.eval_binary_op(*op, l, r)
            }

            Expr::UnaryOp { op, operand } => {
                let v = self.evaluate(operand)?;
                self.eval_unary_op(*op, v)
            }

            Expr::FunctionCall { name, args } => {
                // Evaluate all arguments
                let evaluated_args: Result<Vec<f64>, _> =
                    args.iter().map(|arg| self.evaluate(arg)).collect();
                let evaled_args = evaluated_args?;

                // Look up function
                let func = get_function(name)
                    .ok_or_else(|| FormulaError::UnknownFunction(name.clone()))?;

                // Validate arg count
                if evaled_args.len() < func.min_args || evaled_args.len() > func.max_args {
                    return Err(FormulaError::ArgCount {
                        function: name.clone(),
                        min: func.min_args,
                        max: func.max_args,
                        actual: evaled_args.len(),
                    });
                }

                // Execute function
                (func.evaluator)(&evaled_args)
            }
        }
    }

    /// Evaluate a binary operation.
    fn eval_binary_op(
        &self,
        op: BinaryOperator,
        left: f64,
        right: f64,
    ) -> Result<f64, FormulaError> {
        match op {
            BinaryOperator::Add => Ok(left + right),
            BinaryOperator::Subtract => Ok(left - right),
            BinaryOperator::Multiply => Ok(left * right),
            BinaryOperator::Divide => {
                if right.abs() < f64::EPSILON {
                    Err(FormulaError::DivisionByZero)
                } else {
                    Ok(left / right)
                }
            }
            BinaryOperator::Modulo => {
                if right.abs() < f64::EPSILON {
                    Err(FormulaError::ModuloByZero)
                } else {
                    Ok(left % right)
                }
            }
        }
    }

    /// Evaluate a unary operation.
    fn eval_unary_op(&self, op: UnaryOperator, operand: f64) -> Result<f64, FormulaError> {
        match op {
            UnaryOperator::Plus => Ok(operand),
            UnaryOperator::Minus => Ok(-operand),
        }
    }
}

/// Convenience function to evaluate a formula expression string.
#[allow(dead_code)]
pub fn evaluate_formula(
    expression: &str,
    field_values: HashMap<TargetField, f64>,
) -> Result<f64, FormulaError> {
    use crate::mappings::formula::parser::FormulaParser;

    let ast = FormulaParser::parse(expression)?;
    let context = FormulaContext::new(field_values);
    let evaluator = FormulaEvaluator::new(&context);
    evaluator.evaluate(&ast)
}

/// Convenience function to evaluate a formula expression string with concept resolver.
#[allow(dead_code)]
pub fn evaluate_formula_with_resolver(
    expression: &str,
    field_values: HashMap<TargetField, f64>,
    resolver: &dyn ConceptResolver,
) -> Result<f64, FormulaError> {
    use crate::mappings::formula::parser::FormulaParser;

    let ast = FormulaParser::parse(expression)?;
    let context = FormulaContext::new(field_values).with_concept_resolver(resolver);
    let evaluator = FormulaEvaluator::new(&context);
    evaluator.evaluate(&ast)
}

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

    #[test]
    fn test_evaluate_literal() {
        let expr = Expr::Literal(42.0);
        let context = FormulaContext::empty();
        let evaluator = FormulaEvaluator::new(&context);

        assert_eq!(evaluator.evaluate(&expr).unwrap(), 42.0);
    }

    #[test]
    fn test_evaluate_field_ref() {
        let expr = Expr::FieldRef(TargetField::TotalRevenues);
        let mut field_values = HashMap::new();
        field_values.insert(TargetField::TotalRevenues, 1000.0);

        let context = FormulaContext::new(field_values);
        let evaluator = FormulaEvaluator::new(&context);

        assert_eq!(evaluator.evaluate(&expr).unwrap(), 1000.0);
    }

    #[test]
    fn test_evaluate_unknown_field() {
        let expr = Expr::FieldRef(TargetField::TotalRevenues);
        let context = FormulaContext::empty();
        let evaluator = FormulaEvaluator::new(&context);

        assert!(matches!(
            evaluator.evaluate(&expr),
            Err(FormulaError::UnknownField(_))
        ));
    }

    #[test]
    fn test_evaluate_binary_ops() {
        let expr = Expr::BinaryOp {
            op: BinaryOperator::Add,
            left: Box::new(Expr::Literal(5.0)),
            right: Box::new(Expr::Literal(3.0)),
        };

        let context = FormulaContext::empty();
        let evaluator = FormulaEvaluator::new(&context);

        assert_eq!(evaluator.evaluate(&expr).unwrap(), 8.0);
    }

    #[test]
    fn test_evaluate_division_by_zero() {
        let expr = Expr::BinaryOp {
            op: BinaryOperator::Divide,
            left: Box::new(Expr::Literal(5.0)),
            right: Box::new(Expr::Literal(0.0)),
        };

        let context = FormulaContext::empty();
        let evaluator = FormulaEvaluator::new(&context);

        assert!(matches!(
            evaluator.evaluate(&expr),
            Err(FormulaError::DivisionByZero)
        ));
    }

    #[test]
    fn test_evaluate_function_call() {
        let expr = Expr::FunctionCall {
            name: "SUM".to_string(),
            args: vec![Expr::Literal(1.0), Expr::Literal(2.0), Expr::Literal(3.0)],
        };

        let context = FormulaContext::empty();
        let evaluator = FormulaEvaluator::new(&context);

        assert_eq!(evaluator.evaluate(&expr).unwrap(), 6.0);
    }

    #[test]
    fn test_evaluate_unknown_function() {
        let expr = Expr::FunctionCall {
            name: "UNKNOWN".to_string(),
            args: vec![Expr::Literal(1.0)],
        };

        let context = FormulaContext::empty();
        let evaluator = FormulaEvaluator::new(&context);

        assert!(matches!(
            evaluator.evaluate(&expr),
            Err(FormulaError::UnknownFunction(_))
        ));
    }

    #[test]
    fn test_evaluate_complex_formula() {
        // ($gross_profit / $total_revenues) * 100
        let expr = Expr::BinaryOp {
            op: BinaryOperator::Multiply,
            left: Box::new(Expr::BinaryOp {
                op: BinaryOperator::Divide,
                left: Box::new(Expr::FieldRef(TargetField::GrossProfit)),
                right: Box::new(Expr::FieldRef(TargetField::TotalRevenues)),
            }),
            right: Box::new(Expr::Literal(100.0)),
        };

        let mut field_values = HashMap::new();
        field_values.insert(TargetField::GrossProfit, 400.0);
        field_values.insert(TargetField::TotalRevenues, 1000.0);

        let context = FormulaContext::new(field_values);
        let evaluator = FormulaEvaluator::new(&context);

        assert_eq!(evaluator.evaluate(&expr).unwrap(), 40.0);
    }

    #[test]
    fn test_evaluate_formula_string() {
        let mut field_values = HashMap::new();
        field_values.insert(TargetField::TotalRevenues, 1000.0);
        field_values.insert(TargetField::NetIncome, 200.0);

        let result = evaluate_formula("$total_revenues + $net_income", field_values);
        assert_eq!(result.unwrap(), 1200.0);
    }

    #[test]
    fn test_circular_reference_detection() {
        let expr = Expr::FieldRef(TargetField::TotalRevenues);
        let mut context = FormulaContext::empty();
        context.evaluation_stack.push(TargetField::TotalRevenues); // Already in stack

        let evaluator = FormulaEvaluator::new(&context);
        assert!(matches!(
            evaluator.evaluate(&expr),
            Err(FormulaError::CircularReference(_))
        ));
    }

    #[test]
    fn test_evaluate_concept_ref() {
        let expr = Expr::ConceptRef(crate::mappings::formula::ast::ConceptReference {
            taxonomy: "us-gaap".to_string(),
            concept: "Revenue".to_string(),
            unit_family: crate::terminal::sec_edgar::UnitFamily::Currency,
        });

        let mut resolver = crate::mappings::formula::resolver::XbrlConceptResolver::empty();
        resolver.add_concept(
            "us-gaap".to_string(),
            "Revenue".to_string(),
            crate::terminal::sec_edgar::UnitFamily::Currency,
            5000.0,
        );

        let context = FormulaContext::empty().with_concept_resolver(&resolver);

        let evaluator = FormulaEvaluator::new(&context);
        assert_eq!(evaluator.evaluate(&expr).unwrap(), 5000.0);
    }

    #[test]
    fn test_evaluate_concept_ref_not_found() {
        let expr = Expr::ConceptRef(crate::mappings::formula::ast::ConceptReference {
            taxonomy: "us-gaap".to_string(),
            concept: "NonExistent".to_string(),
            unit_family: crate::terminal::sec_edgar::UnitFamily::Currency,
        });

        let resolver = crate::mappings::formula::resolver::XbrlConceptResolver::empty();

        let context = FormulaContext::empty().with_concept_resolver(&resolver);

        let evaluator = FormulaEvaluator::new(&context);
        assert!(matches!(
            evaluator.evaluate(&expr),
            Err(FormulaError::ConceptNotFound { .. })
        ));
    }

    #[test]
    fn test_evaluate_mixed_formula_with_concepts() {
        // Formula: ($gross_profit + #us-gaap.OtherExpenses[currency]) / $total_revenues
        let expr = Expr::BinaryOp {
            op: BinaryOperator::Divide,
            left: Box::new(Expr::BinaryOp {
                op: BinaryOperator::Add,
                left: Box::new(Expr::FieldRef(TargetField::GrossProfit)),
                right: Box::new(Expr::ConceptRef(
                    crate::mappings::formula::ast::ConceptReference {
                        taxonomy: "us-gaap".to_string(),
                        concept: "OtherExpenses".to_string(),
                        unit_family: crate::terminal::sec_edgar::UnitFamily::Currency,
                    },
                )),
            }),
            right: Box::new(Expr::FieldRef(TargetField::TotalRevenues)),
        };

        let mut field_values = HashMap::new();
        field_values.insert(TargetField::GrossProfit, 400.0);
        field_values.insert(TargetField::TotalRevenues, 1000.0);

        let mut resolver = crate::mappings::formula::resolver::XbrlConceptResolver::empty();
        resolver.add_concept(
            "us-gaap".to_string(),
            "OtherExpenses".to_string(),
            crate::terminal::sec_edgar::UnitFamily::Currency,
            50.0,
        );

        let context = FormulaContext::new(field_values).with_concept_resolver(&resolver);

        let evaluator = FormulaEvaluator::new(&context);
        assert_eq!(evaluator.evaluate(&expr).unwrap(), 0.45); // (400 + 50) / 1000
    }

    #[test]
    fn test_evaluate_formula_with_concept_resolver() {
        let mut field_values = HashMap::new();
        field_values.insert(TargetField::TotalRevenues, 1000.0);

        let mut resolver = crate::mappings::formula::resolver::XbrlConceptResolver::empty();
        resolver.add_concept(
            "us-gaap".to_string(),
            "GrossProfit".to_string(),
            crate::terminal::sec_edgar::UnitFamily::Currency,
            600.0,
        );

        let result = evaluate_formula_with_resolver(
            "#us-gaap.GrossProfit[currency] / $total_revenues * 100",
            field_values,
            &resolver,
        )
        .unwrap();

        assert_eq!(result, 60.0); // (600 / 1000) * 100
    }

    #[test]
    fn test_evaluate_concept_ref_without_resolver() {
        let expr = Expr::ConceptRef(crate::mappings::formula::ast::ConceptReference {
            taxonomy: "us-gaap".to_string(),
            concept: "Revenue".to_string(),
            unit_family: crate::terminal::sec_edgar::UnitFamily::Currency,
        });

        let context = FormulaContext::empty();

        let evaluator = FormulaEvaluator::new(&context);
        assert!(matches!(
            evaluator.evaluate(&expr),
            Err(FormulaError::Evaluation { .. })
        ));
    }
}
