"""
Unit tests for the FlowEngine module
"""

import unittest
import asyncio
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from unittest.mock import Mock, patch, AsyncMock
from optrabot.flowengine.flowengine import FlowEngine
from optrabot.flowengine.flowconfig import (
    Flow, FlowEventConfig, FlowAction,
    SendNotificationAction, ProcessTemplateAction
)
from optrabot.flowengine.flowevent import (
    FlowEventType, EarlyExitEventData, TradeOpenedEventData
)


class FlowEngineTests(unittest.TestCase):
    """Test cases for FlowEngine"""
    
    def test_singleton_pattern(self):
        """Test that FlowEngine follows singleton pattern"""
        engine1 = FlowEngine()
        engine2 = FlowEngine()
        self.assertIs(engine1, engine2, 'Expected FlowEngine to follow singleton pattern')
    
    def test_expression_evaluation_simple(self):
        """Test simple expression evaluation"""
        engine = FlowEngine()
        variables = {'EVENT_TRADE_AMOUNT': 2}
        
        result = engine._evaluate_expression('$EVENT_TRADE_AMOUNT * 2', variables, 'amount')
        self.assertEqual(result, 4, 'Expected expression to evaluate to 4')
    
    def test_expression_evaluation_complex(self):
        """Test complex expression evaluation"""
        engine = FlowEngine()
        variables = {
            'EVENT_TRADE_NET_RESULT': 100,
            'EVENT_TRADE_PREMIUM': 50,
            'EVENT_TRADE_AMOUNT': 2
        }
        
        result = engine._evaluate_expression(
            '($EVENT_TRADE_NET_RESULT + $EVENT_TRADE_PREMIUM) / ($EVENT_TRADE_AMOUNT * 2)',
            variables,
            'premium'
        )
        self.assertEqual(result, 37.5, 'Expected expression to evaluate to 37.5')
    
    def test_expression_evaluation_static_value(self):
        """Test that static values are returned as-is"""
        engine = FlowEngine()
        variables = {}
        
        # Integer
        result = engine._evaluate_expression(5, variables, 'amount')
        self.assertEqual(result, 5, 'Expected static integer value')
        
        # Float
        result = engine._evaluate_expression(0.6, variables, 'premium')
        self.assertEqual(result, 0.6, 'Expected static float value')
    
    def test_expression_evaluation_error(self):
        """Test that invalid expressions raise appropriate errors"""
        engine = FlowEngine()
        variables = {'EVENT_TRADE_AMOUNT': 2}
        
        with self.assertRaises(ValueError) as context:
            engine._evaluate_expression('$INVALID_VAR * 2', variables, 'amount')
        
        self.assertIn('Failed to evaluate', str(context.exception), 'Expected ValueError with message')
    
    def test_send_notification_action(self):
        """Test send_notification action execution"""
        async def run_test():
            engine = FlowEngine()
            
            config = SendNotificationAction(
                message="Test message: $EVENT_TRADE_ID",
                type="INFO"
            )
            
            variables = {'EVENT_TRADE_ID': 123}
            
            flow = Flow(id='test_flow', name='Test Flow')
            
            with patch('optrabot.tradinghubclient.TradinghubClient') as mock_client:
                mock_instance = Mock()
                mock_instance.send_notification = AsyncMock()
                mock_client.return_value = mock_instance
                
                await engine._execute_send_notification(config, variables, flow)
                
                mock_instance.send_notification.assert_called_once()
                # Check that the message was interpolated
                call_args = mock_instance.send_notification.call_args
                self.assertIn('123', str(call_args), 'Expected trade_id in notification message')
        
        # Run async test
        asyncio.run(run_test())
    
    def test_flow_event_data_variables(self):
        """Test that event data provides correct variables"""
        from datetime import date
        
        event_data = EarlyExitEventData(
            event_type=FlowEventType.EARLY_EXIT,
            trade_id=123,
            trade_amount=2,
            trade_symbol='SPX',
            trade_strategy='Test Strategy',
            template_name='TestTemplate',
            trade_expiration=date(2025, 12, 20),
            trade_group_id='test-group-123',
            trade_entry_price=100.0,
            trade_exit_price=110.0,
            trade_net_result=50.0,
            trade_premium=25.0,
            trade_fees=10.0
        )
        
        variables = event_data.get_variables()
        
        self.assertEqual(variables['EVENT_TRADE_ID'], 123, 'Expected correct trade_id')
        self.assertEqual(variables['EVENT_TRADE_AMOUNT'], 2, 'Expected correct trade_amount')
        self.assertEqual(variables['EVENT_TRADE_SYMBOL'], 'SPX', 'Expected correct trade_symbol')
        self.assertEqual(variables['EVENT_TRADE_STRATEGY'], 'Test Strategy', 'Expected correct strategy')
        self.assertEqual(variables['EVENT_TRADE_FEES'], 10.0, 'Expected correct trade_fees')
        self.assertEqual(variables['EVENT_TRADE_ENTRY_PRICE'], 100.0, 'Expected correct entry_price')
        self.assertEqual(variables['EVENT_TRADE_EXIT_PRICE'], 110.0, 'Expected correct exit_price')
        self.assertEqual(variables['EVENT_TRADE_NET_RESULT'], 50.0, 'Expected correct net_result')
        self.assertEqual(variables['EVENT_TRADE_PREMIUM'], 25.0, 'Expected correct premium')
    
    def test_trade_opened_event_data_variables(self):
        """Test that trade_opened event data provides correct variables"""
        from datetime import date
        
        event_data = TradeOpenedEventData(
            event_type=FlowEventType.TRADE_OPENED,
            trade_id=456,
            trade_amount=3,
            trade_symbol='SPX',
            trade_strategy='Another Strategy',
            template_name='AnotherTemplate',
            trade_expiration=date(2025, 12, 20),
            trade_group_id='test-group-456',
            trade_entry_price=200.0
        )
        
        variables = event_data.get_variables()
        
        self.assertEqual(variables['EVENT_TRADE_ID'], 456, 'Expected correct trade_id')
        self.assertEqual(variables['EVENT_TRADE_AMOUNT'], 3, 'Expected correct trade_amount')
        self.assertEqual(variables['EVENT_TRADE_ENTRY_PRICE'], 200.0, 'Expected correct entry_price')
        # Verify that exit-specific variables are not present
        self.assertNotIn('EVENT_TRADE_EXIT_PRICE', variables, 'Exit price should not be in trade_opened event')

    def test_template_expiration_date_priority(self):
        """Test that set_expiration_date() has priority over dte"""
        from datetime import date
        from optrabot.tradetemplate.templatefactory import IronFly
        
        # Create a template with DTE = 1
        template = IronFly('TestTemplate')
        template.dte = 1
        
        # Initially, expiration_date should be None
        self.assertIsNone(template.expiration_date, 'Expected expiration_date to be None initially')
        
        # Set explicit expiration date
        explicit_date = date(2025, 12, 25)
        template.set_expiration_date(explicit_date)
        
        # Verify that expiration_date is set
        self.assertEqual(template.expiration_date, explicit_date, 'Expected expiration_date to be set')
        
        # Verify that dte is still preserved (but will be overridden in composeEntryOrder)
        self.assertEqual(template.dte, 1, 'Expected dte to still be 1')

    def test_time_scheduling_past_executes_immediately(self):
        """Test that time in the past executes immediately instead of raising error"""
        async def run_test():
            from datetime import datetime, timedelta
            import pytz
            from optrabot.flowengine.flowconfig import ProcessTemplateAction
            from optrabot.tradetemplate.templatetrigger import TemplateTrigger
            
            engine = FlowEngine()
            
            # Get a time in the past (1 hour ago) and parse it to datetime
            past_time = datetime.now(pytz.timezone('US/Eastern')) - timedelta(hours=1)
            time_str = past_time.strftime('%H:%M EST')
            
            # Parse the time string to datetime (simulating config loading)
            trigger = TemplateTrigger({'type': 'time', 'value': time_str})
            parsed_time = trigger.parseTimeWithTimezone(time_str)
            
            config = ProcessTemplateAction(
                template='TestTemplate',
                amount=2,
                premium=0.65,
                time=parsed_time  # Now passing datetime object
            )
            
            variables = {}
            flow = Flow(id='test_flow', name='Test Flow')
            
            # Mock the config and template
            with patch('optrabot.config.appConfig') as mock_config:
                mock_template = Mock()
                mock_template.name = 'TestTemplate'
                mock_template.is_enabled = Mock(return_value=True)
                mock_config.getTemplates = Mock(return_value=[mock_template])
                
                # Mock TemplateProcessor to track if processTemplate was called
                # TemplateProcessor is imported inside the function, so we patch it there
                with patch('optrabot.tradetemplate.processor.templateprocessor.TemplateProcessor') as mock_processor_class:
                    mock_processor = Mock()
                    mock_processor.processTemplate = AsyncMock()
                    mock_processor_class.return_value = mock_processor
                    
                    # Should execute immediately without error
                    await engine._execute_process_template(config, variables, flow)
                    
                    # Verify that processTemplate was called (immediate execution)
                    mock_processor.processTemplate.assert_called_once()
        
        asyncio.run(run_test())

    def test_time_parsing_valid_format(self):
        """Test that valid time formats are accepted"""
        from optrabot.tradetemplate.templatetrigger import TemplateTrigger
        
        # Test various valid time formats (EST is converted to US/Eastern internally)
        valid_times = [
            "15:00 EST",
            "14:45 EST",
            "12:00 UTC"
        ]
        
        for time_str in valid_times:
            trigger = TemplateTrigger({'type': 'time', 'value': time_str})
            parsed_time = trigger.parseTimeWithTimezone(time_str)
            self.assertIsNotNone(parsed_time, f'Expected valid time for "{time_str}"')


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

