"""
Unit tests for FlowEngine expression evaluation with mathematical functions and EVENT_TRADE_FEES
"""

import unittest
from unittest.mock import Mock, patch, AsyncMock
from datetime import date

from optrabot.flowengine.flowengine import FlowEngine
from optrabot.flowengine.flowevent import EarlyExitEventData, FlowEventType
from optrabot.flowengine.flowconfig import FlowActionType, ProcessTemplateAction


class TestFlowEngineExpressions(unittest.TestCase):
    """Test expression evaluation in FlowEngine"""
    
    def setUp(self):
        """Set up test fixtures"""
        self.engine = FlowEngine()
    
    def test_event_trade_fees_variable_available(self):
        """Test that EVENT_TRADE_FEES variable is available in event data"""
        event_data = EarlyExitEventData(
            event_type=FlowEventType.EARLY_EXIT,
            trade_id=888,
            trade_amount=1,
            trade_symbol='SPX',
            trade_strategy='0DTE IC 100 Income',
            template_name='0DTEIC100Income',
            trade_expiration=date(2025, 10, 22),
            trade_group_id='test-group-888',
            trade_entry_price=0.5,
            trade_exit_price=0.25,
            trade_net_result=-50.0,
            trade_premium=250.0,
            trade_fees=15.50
        )
        
        variables = event_data.get_variables()
        
        # Verify EVENT_TRADE_FEES is present
        self.assertIn('EVENT_TRADE_FEES', variables)
        self.assertEqual(variables['EVENT_TRADE_FEES'], 15.50)
    
    def test_abs_function_in_expression(self):
        """Test that abs() function works in expressions"""
        variables = {
            'EVENT_TRADE_NET_RESULT': -50.0,
            'EVENT_TRADE_PREMIUM': 250.0,
            'EVENT_TRADE_AMOUNT': 1
        }
        
        # Test abs() with negative value
        expression = 'abs($EVENT_TRADE_NET_RESULT)'
        result = self.engine._evaluate_expression(expression, variables, 'test')
        self.assertEqual(result, 50.0)
        
        # Test abs() in complex formula
        expression = '(abs($EVENT_TRADE_NET_RESULT) + $EVENT_TRADE_PREMIUM) / $EVENT_TRADE_AMOUNT'
        result = self.engine._evaluate_expression(expression, variables, 'test')
        self.assertEqual(result, 300.0)
    
    def test_min_function_in_expression(self):
        """Test that min() function works in expressions"""
        variables = {
            'EVENT_TRADE_PREMIUM': 250.0,
            'EVENT_TRADE_FEES': 15.50
        }
        
        expression = 'min($EVENT_TRADE_PREMIUM, 100)'
        result = self.engine._evaluate_expression(expression, variables, 'test')
        self.assertEqual(result, 100)
        
        expression = 'min($EVENT_TRADE_FEES, 20)'
        result = self.engine._evaluate_expression(expression, variables, 'test')
        self.assertEqual(result, 15.50)
    
    def test_max_function_in_expression(self):
        """Test that max() function works in expressions"""
        variables = {
            'EVENT_TRADE_NET_RESULT': -50.0,
            'EVENT_TRADE_FEES': 15.50
        }
        
        expression = 'max(abs($EVENT_TRADE_NET_RESULT), $EVENT_TRADE_FEES)'
        result = self.engine._evaluate_expression(expression, variables, 'test')
        self.assertEqual(result, 50.0)
    
    def test_round_function_in_expression(self):
        """Test that round() function works in expressions"""
        variables = {
            'EVENT_TRADE_PREMIUM': 250.0
        }
        
        expression = 'round($EVENT_TRADE_PREMIUM / 3, 2)'
        result = self.engine._evaluate_expression(expression, variables, 'test')
        self.assertAlmostEqual(result, 83.33, places=2)
    
    def test_config_formula_iic_rollover1(self):
        """Test the formula from iic_rollover1 in config.yaml"""
        variables = {
            'EVENT_TRADE_AMOUNT': 1,
            'EVENT_TRADE_NET_RESULT': -50.0,
            'EVENT_TRADE_PREMIUM': 250.0,
            'EVENT_TRADE_FEES': 15.50
        }
        
        # Formula from config: (abs($EVENT_TRADE_NET_RESULT) + $EVENT_TRADE_PREMIUM + (50 * $EVENT_TRADE_AMOUNT) + (($EVENT_TRADE_FEES * 2))) / ($EVENT_TRADE_AMOUNT * 2) / 100
        expression = '(abs($EVENT_TRADE_NET_RESULT) + $EVENT_TRADE_PREMIUM + (50 * $EVENT_TRADE_AMOUNT) + (($EVENT_TRADE_FEES * 2))) / ($EVENT_TRADE_AMOUNT * 2) / 100'
        result = self.engine._evaluate_expression(expression, variables, 'premium')
        
        # Expected: (50 + 250 + 50 + 31) / 2 / 100 = 381 / 2 / 100 = 1.905
        self.assertAlmostEqual(result, 1.905, places=3)
    
    def test_config_formula_iic_rollover2(self):
        """Test the formula from iic_rollover2 in config.yaml"""
        variables = {
            'EVENT_TRADE_AMOUNT': 1,
            'EVENT_TRADE_NET_RESULT': -50.0,
            'EVENT_TRADE_PREMIUM': 250.0
        }
        
        # Formula from config: (abs($EVENT_TRADE_NET_RESULT) + $EVENT_TRADE_PREMIUM) / ($EVENT_TRADE_AMOUNT * 1.5)
        expression = '(abs($EVENT_TRADE_NET_RESULT) + $EVENT_TRADE_PREMIUM) / ($EVENT_TRADE_AMOUNT * 1.5)'
        result = self.engine._evaluate_expression(expression, variables, 'premium')
        
        # Expected: (50 + 250) / 1.5 = 300 / 1.5 = 200
        self.assertEqual(result, 200.0)
    
    def test_fees_in_complex_formula(self):
        """Test fees calculation in complex premium formula"""
        variables = {
            'EVENT_TRADE_AMOUNT': 2,
            'EVENT_TRADE_NET_RESULT': -100.0,
            'EVENT_TRADE_PREMIUM': 500.0,
            'EVENT_TRADE_FEES': 25.0
        }
        
        # Test formula that includes fees
        expression = '(abs($EVENT_TRADE_NET_RESULT) + $EVENT_TRADE_PREMIUM + $EVENT_TRADE_FEES) / $EVENT_TRADE_AMOUNT'
        result = self.engine._evaluate_expression(expression, variables, 'premium')
        
        # Expected: (100 + 500 + 25) / 2 = 625 / 2 = 312.5
        self.assertEqual(result, 312.5)
    
    def test_fees_multiplication_in_formula(self):
        """Test fees multiplication in formula (compensating for future fees)"""
        variables = {
            'EVENT_TRADE_FEES': 15.50
        }
        
        # Test doubling fees (for example, to account for both closing and opening fees)
        expression = '$EVENT_TRADE_FEES * 2'
        result = self.engine._evaluate_expression(expression, variables, 'test')
        self.assertEqual(result, 31.0)
    
    def test_nested_functions(self):
        """Test nested function calls"""
        variables = {
            'EVENT_TRADE_NET_RESULT': -75.5,
            'EVENT_TRADE_FEES': 10.0
        }
        
        # Round the absolute value
        expression = 'round(abs($EVENT_TRADE_NET_RESULT), 0)'
        result = self.engine._evaluate_expression(expression, variables, 'test')
        self.assertEqual(result, 76.0)
    
    def test_expression_with_all_variables(self):
        """Test expression using all available exit event variables"""
        variables = {
            'EVENT_TRADE_ID': 888,
            'EVENT_TRADE_AMOUNT': 1,
            'EVENT_TRADE_ENTRY_PRICE': 0.5,
            'EVENT_TRADE_EXIT_PRICE': 0.25,
            'EVENT_TRADE_NET_RESULT': -50.0,
            'EVENT_TRADE_PREMIUM': 250.0,
            'EVENT_TRADE_FEES': 15.50
        }
        
        # Complex formula using multiple variables
        expression = '($EVENT_TRADE_PREMIUM - abs($EVENT_TRADE_NET_RESULT) - $EVENT_TRADE_FEES) / $EVENT_TRADE_AMOUNT'
        result = self.engine._evaluate_expression(expression, variables, 'test')
        
        # Expected: (250 - 50 - 15.5) / 1 = 184.5
        self.assertEqual(result, 184.5)


class TestFlowEngineFeeCalculation(unittest.TestCase):
    """Test fee calculation and integration in flow events"""
    
    def test_trade_exit_event_includes_fees(self):
        """Test that trade exit events include fees data"""
        event_data = EarlyExitEventData(
            event_type=FlowEventType.EARLY_EXIT,
            trade_id=100,
            trade_amount=2,
            trade_symbol='SPX',
            trade_strategy='Test Strategy',
            template_name='TestTemplate',
            trade_expiration=date(2025, 10, 22),
            trade_group_id='test-group-100',
            trade_entry_price=1.0,
            trade_exit_price=0.5,
            trade_net_result=-100.0,
            trade_premium=400.0,
            trade_fees=30.0
        )
        
        # Verify fees are stored
        self.assertEqual(event_data.trade_fees, 30.0)
        
        # Verify fees are in variables
        variables = event_data.get_variables()
        self.assertEqual(variables['EVENT_TRADE_FEES'], 30.0)
    
    def test_realistic_rollover_scenario(self):
        """Test realistic rollover scenario with fees"""
        # Scenario: 0DTE IC closed at breakeven with fees
        variables = {
            'EVENT_TRADE_AMOUNT': 1,
            'EVENT_TRADE_NET_RESULT': -20.0,  # Small loss after fees
            'EVENT_TRADE_PREMIUM': 300.0,      # Original premium received
            'EVENT_TRADE_FEES': 20.0            # Total fees (open + close)
        }
        
        engine = FlowEngine()
        
        # Calculate new amount (doubling)
        amount_expr = '$EVENT_TRADE_AMOUNT * 2'
        new_amount = engine._evaluate_expression(amount_expr, variables, 'amount')
        self.assertEqual(new_amount, 2)
        
        # Calculate new premium to recover loss and fees
        premium_expr = '(abs($EVENT_TRADE_NET_RESULT) + $EVENT_TRADE_PREMIUM + $EVENT_TRADE_FEES) / ($EVENT_TRADE_AMOUNT * 2)'
        new_premium = engine._evaluate_expression(premium_expr, variables, 'premium')
        
        # Expected: (20 + 300 + 20) / 2 = 340 / 2 = 170
        self.assertEqual(new_premium, 170.0)


if __name__ == '__main__':
    unittest.main()
