"""Tests for KPI calculator."""

import pytest

from src.config import KPINodeConfig, NodeType, KPICategory
from src.kpi.tree import KPITree
from src.kpi.calculator import KPICalculator, FormulaParser, FormulaError


class TestFormulaParser:
    """Tests for FormulaParser."""

    @pytest.fixture
    def parser(self):
        return FormulaParser()

    def test_parse_variables(self, parser):
        """Test variable extraction."""
        formula = "{a} + {b} * {c}"
        variables = parser.parse_variables(formula)
        assert variables == ["a", "b", "c"]

    def test_substitute_variables(self, parser):
        """Test variable substitution."""
        formula = "{a} + {b}"
        values = {"a": 10, "b": 20}
        result = parser.substitute_variables(formula, values)
        assert result == "10 + 20"

    def test_missing_variable_raises(self, parser):
        """Test that missing variables raise error."""
        formula = "{a} + {b}"
        values = {"a": 10}
        with pytest.raises(FormulaError, match="Missing variable"):
            parser.substitute_variables(formula, values)

    @pytest.mark.parametrize(
        "formula,values,expected",
        [
            ("{a} + {b}", {"a": 10, "b": 5}, 15),
            ("{a} - {b}", {"a": 10, "b": 5}, 5),
            ("{a} * {b}", {"a": 10, "b": 5}, 50),
            ("{a} / {b}", {"a": 10, "b": 5}, 2),
            ("({a} + {b}) * {c}", {"a": 10, "b": 5, "c": 2}, 30),
            ("{a} * {b} + {c}", {"a": 10, "b": 5, "c": 3}, 53),
        ],
    )
    def test_evaluate_basic_operations(self, parser, formula, values, expected):
        """Test basic arithmetic operations."""
        result = parser.evaluate(formula, values)
        assert result == expected

    def test_evaluate_division_by_zero(self, parser):
        """Test division by zero returns 0."""
        result = parser.evaluate("{a} / {b}", {"a": 10, "b": 0})
        assert result == 0.0

    def test_evaluate_decimal(self, parser):
        """Test decimal calculations."""
        result = parser.evaluate("{a} / {b}", {"a": 10, "b": 3})
        assert abs(result - 3.333) < 0.01

    def test_evaluate_negative(self, parser):
        """Test negative numbers."""
        result = parser.evaluate("-{a}", {"a": 10})
        assert result == -10

    def test_invalid_expression_raises(self, parser):
        """Test that invalid expressions raise error."""
        with pytest.raises(FormulaError):
            parser.evaluate("{a} @@ {b}", {"a": 10, "b": 5})

    def test_dangerous_code_blocked(self, parser):
        """Test that dangerous code is blocked."""
        dangerous_expressions = [
            "__import__('os')",
            "eval('1+1')",
            "exec('print(1)')",
        ]
        for expr in dangerous_expressions:
            with pytest.raises(FormulaError):
                parser.evaluate(expr, {})


class TestKPICalculator:
    """Tests for KPICalculator."""

    @pytest.fixture
    def simple_tree_config(self):
        """Simple tree: revenue = contracts * avg_price."""
        return [
            KPINodeConfig(
                id="revenue",
                name="Revenue",
                type=NodeType.KGI,
                formula="{contracts} * {avg_price}",
                children=["contracts", "avg_price"],
            ),
            KPINodeConfig(
                id="contracts",
                name="Contracts",
                type=NodeType.INPUT,
            ),
            KPINodeConfig(
                id="avg_price",
                name="Avg Price",
                type=NodeType.INPUT,
            ),
        ]

    @pytest.fixture
    def complex_tree_config(self):
        """Complex tree with multiple levels."""
        return [
            KPINodeConfig(
                id="revenue",
                name="Revenue",
                type=NodeType.KGI,
                formula="{contracts} * {avg_price}",
                children=["contracts", "avg_price"],
            ),
            KPINodeConfig(
                id="contracts",
                name="Contracts",
                type=NodeType.KPI,
                formula="{meetings} * {close_rate}",
                children=["meetings", "close_rate"],
            ),
            KPINodeConfig(
                id="avg_price",
                name="Avg Price",
                type=NodeType.INPUT,
            ),
            KPINodeConfig(
                id="meetings",
                name="Meetings",
                type=NodeType.KPI,
                formula="{leads} * {meeting_rate}",
                children=["leads", "meeting_rate"],
            ),
            KPINodeConfig(
                id="close_rate",
                name="Close Rate",
                type=NodeType.INPUT,
            ),
            KPINodeConfig(
                id="leads",
                name="Leads",
                type=NodeType.INPUT,
            ),
            KPINodeConfig(
                id="meeting_rate",
                name="Meeting Rate",
                type=NodeType.INPUT,
            ),
        ]

    def test_simple_calculation(self, simple_tree_config):
        """Test simple tree calculation."""
        tree = KPITree.from_config(simple_tree_config)
        tree.set_values({"contracts": 100, "avg_price": 5000})

        calculator = KPICalculator(tree)
        results = calculator.calculate()

        assert results["revenue"] == 500000
        assert results["contracts"] == 100
        assert results["avg_price"] == 5000

    def test_complex_calculation(self, complex_tree_config):
        """Test complex tree calculation."""
        tree = KPITree.from_config(complex_tree_config)
        tree.set_values(
            {
                "leads": 1000,
                "meeting_rate": 0.3,
                "close_rate": 0.2,
                "avg_price": 500000,
            }
        )

        calculator = KPICalculator(tree)
        results = calculator.calculate()

        # meetings = 1000 * 0.3 = 300
        assert results["meetings"] == 300

        # contracts = 300 * 0.2 = 60
        assert results["contracts"] == 60

        # revenue = 60 * 500000 = 30,000,000
        assert results["revenue"] == 30000000

    def test_calculation_order(self, complex_tree_config):
        """Test that nodes are calculated in correct order."""
        tree = KPITree.from_config(complex_tree_config)
        calculator = KPICalculator(tree)

        order = calculator._get_calculation_order()

        # Input nodes should come before calculated nodes
        assert order.index("leads") < order.index("meetings")
        assert order.index("meeting_rate") < order.index("meetings")
        assert order.index("meetings") < order.index("contracts")
        assert order.index("contracts") < order.index("revenue")

    def test_simulate(self, complex_tree_config):
        """Test simulation doesn't modify original values."""
        tree = KPITree.from_config(complex_tree_config)
        tree.set_values(
            {
                "leads": 1000,
                "meeting_rate": 0.3,
                "close_rate": 0.2,
                "avg_price": 500000,
            }
        )

        calculator = KPICalculator(tree)

        # Original calculation
        original = calculator.calculate()

        # Simulate changing close_rate
        simulated = calculator.simulate({"close_rate": 0.25})

        # Original should be unchanged
        assert tree.nodes["close_rate"].value == 0.2

        # Simulation should show new value
        # contracts = 300 * 0.25 = 75
        # revenue = 75 * 500000 = 37,500,000
        assert simulated["contracts"] == 75
        assert simulated["revenue"] == 37500000
