"""
Tests for strategy execution (run/stop/status) flow.

These tests cover the new run_strategy, stop_strategy, and get_strategy_status
message handlers and their underlying implementation.
"""
import pytest
import json
from unittest.mock import MagicMock, patch


class TestStartStrategyValidation:
    """Tests for start_strategy input validation and authorization."""

    @pytest.fixture
    def mock_brighter_trades(self):
        """Create a mock BrighterTrades instance with required dependencies."""
        with patch.dict('sys.modules', {'eventlet': MagicMock()}):
            from BrighterTrades import BrighterTrades

            # Create mock dependencies
            mock_socketio = MagicMock()

            with patch.object(BrighterTrades, '__init__', lambda x, y: None):
                bt = BrighterTrades(mock_socketio)

                # Set up required attributes
                bt.strategies = MagicMock()
                bt.strategies.active_instances = {}
                bt.strategies.data_cache = MagicMock()

                # Mock users dependency
                bt.users = MagicMock()
                bt.users.get_username = MagicMock(return_value='test_user')

                # Mock get_user_info
                bt.get_user_info = MagicMock(return_value='test_user')

                # Mock exchanges
                bt.exchanges = MagicMock()
                bt.exchanges.get_price = MagicMock(return_value=50000.0)
                bt.users.get_exchanges = MagicMock(return_value=['binance'])
                bt.users.get_api_keys = MagicMock(return_value={'key': 'k', 'secret': 's'})
                mock_exchange = MagicMock()
                mock_exchange.testnet = True
                mock_exchange.configured = True
                bt.exchanges.get_exchange = MagicMock(return_value=mock_exchange)
                bt.exchanges.connect_exchange = MagicMock(return_value=True)

                # Mock EDM client for exchange validation
                bt.edm_client = MagicMock()
                bt.edm_client.get_exchanges_sync = MagicMock(return_value=['binance', 'kucoin'])

                # Mock strategies.get_strategy_by_tbl_key for exchange validation
                bt.strategies.get_strategy_by_tbl_key = MagicMock(return_value={
                    'strategy_components': {},
                    'default_source': {}
                })

                return bt

    def test_start_strategy_invalid_mode(self, mock_brighter_trades):
        """Test that invalid mode returns error."""
        result = mock_brighter_trades.start_strategy(
            user_id=1,
            strategy_id='test-strategy',
            mode='invalid_mode'
        )

        assert result['success'] is False
        assert 'Invalid mode' in result['message']

    def test_start_strategy_strategy_not_found(self, mock_brighter_trades):
        """Test that missing strategy returns error."""
        # Mock empty result from database
        mock_brighter_trades.strategies.data_cache.get_rows_from_datacache.return_value = MagicMock(empty=True)

        result = mock_brighter_trades.start_strategy(
            user_id=1,
            strategy_id='nonexistent',
            mode='paper'
        )

        assert result['success'] is False
        assert 'not found' in result['message']

    def test_start_strategy_authorization_check(self, mock_brighter_trades):
        """Test that non-owner, non-subscriber cannot run private strategy."""
        import pandas as pd

        # Mock strategy owned by different user (creator is user_id 2, not 1)
        mock_strategy = pd.DataFrame([{
            'tbl_key': 'test-strategy',
            'name': 'Test Strategy',
            'creator': 2,  # Different user_id
            'public': False,
            'strategy_components': json.dumps({'generated_code': 'pass'})
        }])
        mock_brighter_trades.strategies.data_cache.get_rows_from_datacache.return_value = mock_strategy

        # Mock that user is NOT subscribed
        mock_brighter_trades.strategies.is_subscribed = MagicMock(return_value=False)

        result = mock_brighter_trades.start_strategy(
            user_id=1,
            strategy_id='test-strategy',
            mode='paper'
        )

        assert result['success'] is False
        assert 'subscribe' in result['message'].lower()

    def test_start_strategy_authorization_non_subscriber_denied(self, mock_brighter_trades):
        """
        Test that non-owner, non-subscriber cannot run strategy (even private).
        """
        import pandas as pd

        mock_strategy = pd.DataFrame([{
            'tbl_key': 'test-strategy',
            'name': 'Test Strategy',
            'creator': 2,  # Different user_id
            'public': False,
            'strategy_components': json.dumps({'generated_code': 'pass'})
        }])
        mock_brighter_trades.strategies.data_cache.get_rows_from_datacache.return_value = mock_strategy

        # Mock that user is NOT subscribed
        mock_brighter_trades.strategies.is_subscribed = MagicMock(return_value=False)

        result = mock_brighter_trades.start_strategy(
            user_id=1,
            strategy_id='test-strategy',
            mode='paper'
        )

        assert result['success'] is False
        assert 'subscribe' in result['message'].lower()

    def test_start_strategy_live_mode_uses_live_active_instance_key(self, mock_brighter_trades):
        """Live mode now runs in actual live mode with proper instance keying."""
        import pandas as pd

        # Strategy owned by the running user (no subscription needed)
        mock_strategy = pd.DataFrame([{
            'tbl_key': 'test-strategy',
            'name': 'Test Strategy',
            'creator': 'test_user',  # Same as mock user (user_id=1)
            'public': True,
            'strategy_components': json.dumps({'generated_code': 'pass'})
        }])
        mock_brighter_trades.strategies.data_cache.get_rows_from_datacache.return_value = mock_strategy
        mock_brighter_trades.strategies.create_strategy_instance = MagicMock()
        mock_brighter_trades.strategies.create_strategy_instance.return_value = MagicMock(
            strategy_name='Test Strategy'
        )

        result = mock_brighter_trades.start_strategy(
            user_id=1,
            strategy_id='test-strategy',
            mode='live'
        )

        assert result['success'] is True
        assert result['actual_mode'] == 'live'
        assert (1, 'test-strategy', 'live') in mock_brighter_trades.strategies.active_instances

    def test_start_strategy_subscribed_strategy_allowed(self, mock_brighter_trades):
        """Test that a subscribed user can run a public strategy."""
        import pandas as pd

        # Mock public strategy owned by different user
        mock_strategy = pd.DataFrame([{
            'tbl_key': 'test-strategy',
            'name': 'Public Strategy',
            'creator': 'other_user',
            'public': True,
            'strategy_components': json.dumps({'generated_code': 'pass'})
        }])
        mock_brighter_trades.strategies.data_cache.get_rows_from_datacache.return_value = mock_strategy
        mock_brighter_trades.strategies.create_strategy_instance = MagicMock()
        mock_brighter_trades.strategies.create_strategy_instance.return_value = MagicMock(
            strategy_name='Public Strategy'
        )
        # Mock the subscription check to return True
        mock_brighter_trades.strategies.is_subscribed = MagicMock(return_value=True)

        result = mock_brighter_trades.start_strategy(
            user_id=1,
            strategy_id='test-strategy',
            mode='paper'
        )

        assert result['success'] is True

    def test_start_strategy_unsubscribed_public_strategy_denied(self, mock_brighter_trades):
        """Test that unsubscribed user cannot run a public strategy they don't own."""
        import pandas as pd

        # Mock public strategy owned by different user
        mock_strategy = pd.DataFrame([{
            'tbl_key': 'test-strategy',
            'name': 'Public Strategy',
            'creator': 'other_user',
            'public': True,
            'strategy_components': json.dumps({'generated_code': 'pass'})
        }])
        mock_brighter_trades.strategies.data_cache.get_rows_from_datacache.return_value = mock_strategy
        # Mock the subscription check to return False
        mock_brighter_trades.strategies.is_subscribed = MagicMock(return_value=False)

        result = mock_brighter_trades.start_strategy(
            user_id=1,
            strategy_id='test-strategy',
            mode='paper'
        )

        assert result['success'] is False
        assert 'subscribe' in result['message'].lower()

    def test_start_strategy_already_running(self, mock_brighter_trades):
        """Test that strategy cannot be started twice in same mode."""
        import pandas as pd

        mock_strategy = pd.DataFrame([{
            'tbl_key': 'test-strategy',
            'name': 'Test Strategy',
            'creator': 'test_user',
            'public': False,
            'strategy_components': json.dumps({'generated_code': 'pass'})
        }])
        mock_brighter_trades.strategies.data_cache.get_rows_from_datacache.return_value = mock_strategy

        # Mark as already running
        mock_brighter_trades.strategies.active_instances[(1, 'test-strategy', 'paper')] = MagicMock()

        result = mock_brighter_trades.start_strategy(
            user_id=1,
            strategy_id='test-strategy',
            mode='paper'
        )

        assert result['success'] is False
        assert 'already running' in result['message']

    def test_start_strategy_no_generated_code(self, mock_brighter_trades):
        """Test that strategy without code returns error."""
        import pandas as pd

        # Strategy with empty code
        mock_strategy = pd.DataFrame([{
            'tbl_key': 'test-strategy',
            'name': 'Empty Strategy',
            'creator': 'test_user',
            'public': False,
            'strategy_components': json.dumps({'generated_code': ''})
        }])
        mock_brighter_trades.strategies.data_cache.get_rows_from_datacache.return_value = mock_strategy

        result = mock_brighter_trades.start_strategy(
            user_id=1,
            strategy_id='test-strategy',
            mode='paper'
        )

        assert result['success'] is False
        assert 'no generated code' in result['message']

    def test_start_strategy_wrong_code_key(self, mock_brighter_trades):
        """Test that old 'code' key doesn't work (must be 'generated_code')."""
        import pandas as pd

        # Strategy with old key name
        mock_strategy = pd.DataFrame([{
            'tbl_key': 'test-strategy',
            'name': 'Old Format Strategy',
            'creator': 'test_user',
            'public': False,
            'strategy_components': json.dumps({'code': 'pass'})  # Wrong key!
        }])
        mock_brighter_trades.strategies.data_cache.get_rows_from_datacache.return_value = mock_strategy

        result = mock_brighter_trades.start_strategy(
            user_id=1,
            strategy_id='test-strategy',
            mode='paper'
        )

        assert result['success'] is False
        assert 'no generated code' in result['message']

    def test_start_strategy_testnet_override_bypasses_prod_gate(self, mock_brighter_trades, monkeypatch):
        """If config forces testnet, a non-testnet request should not be blocked as production."""
        import pandas as pd
        import config

        mock_strategy = pd.DataFrame([{
            'tbl_key': 'test-strategy',
            'name': 'Test Strategy',
            'creator': 'test_user',
            'public': False,
            'strategy_components': json.dumps({'generated_code': 'pass'})
        }])
        mock_brighter_trades.strategies.data_cache.get_rows_from_datacache.return_value = mock_strategy
        mock_brighter_trades.strategies.create_strategy_instance = MagicMock()
        mock_brighter_trades.strategies.create_strategy_instance.return_value = MagicMock(
            strategy_name='Test Strategy'
        )

        monkeypatch.setattr(config, 'TESTNET_MODE', True, raising=False)
        monkeypatch.setattr(config, 'ALLOW_LIVE_PRODUCTION', False, raising=False)

        result = mock_brighter_trades.start_strategy(
            user_id=1,
            strategy_id='test-strategy',
            mode='live',
            testnet=False
        )

        assert result['success'] is True
        assert result['testnet'] is True


class TestStopStrategy:
    """Tests for stop_strategy functionality."""

    @pytest.fixture
    def mock_brighter_trades(self):
        """Create a mock BrighterTrades instance."""
        with patch.dict('sys.modules', {'eventlet': MagicMock()}):
            from BrighterTrades import BrighterTrades

            with patch.object(BrighterTrades, '__init__', lambda x, y: None):
                bt = BrighterTrades(MagicMock())
                bt.strategies = MagicMock()
                bt.strategies.active_instances = {}
                # Mock wallet_manager for fee settlement
                bt.wallet_manager = MagicMock()
                bt.wallet_manager.settle_accumulated_fees.return_value = {'success': True, 'settled': 0}
                return bt

    def test_stop_strategy_not_running(self, mock_brighter_trades):
        """Test stopping a strategy that isn't running."""
        result = mock_brighter_trades.stop_strategy(
            user_id=1,
            strategy_id='test-strategy',
            mode='paper'
        )

        assert result['success'] is False
        assert 'not running' in result['message'].lower() or 'No running strategy' in result['message']

    def test_stop_strategy_success(self, mock_brighter_trades):
        """Test successfully stopping a running strategy."""
        # Set up running strategy
        mock_instance = MagicMock()
        mock_instance.strategy_name = 'Test Strategy'
        mock_instance.broker = MagicMock()
        mock_instance.broker.get_balance.return_value = 10500.0
        mock_instance.broker.get_available_balance.return_value = 10500.0
        mock_instance.trade_history = [{'pnl': 100}, {'pnl': 400}]

        mock_brighter_trades.strategies.active_instances[(1, 'test-strategy', 'paper')] = mock_instance

        result = mock_brighter_trades.stop_strategy(
            user_id=1,
            strategy_id='test-strategy',
            mode='paper'
        )

        assert result['success'] is True
        assert 'stopped' in result['message']
        assert result['final_stats']['final_balance'] == 10500.0
        assert result['final_stats']['total_trades'] == 2

        # Verify removed from active instances
        assert (1, 'test-strategy', 'paper') not in mock_brighter_trades.strategies.active_instances

    def test_stop_strategy_live_mode_falls_back_to_paper_instance(self, mock_brighter_trades):
        """Stopping live should find fallback paper instance."""
        mock_instance = MagicMock()
        mock_instance.strategy_name = 'Test Strategy'
        mock_instance.broker = MagicMock()
        mock_instance.broker.get_balance.return_value = 10000.0
        mock_instance.broker.get_available_balance.return_value = 10000.0
        mock_instance.trade_history = []

        mock_brighter_trades.strategies.active_instances[(1, 'test-strategy', 'paper')] = mock_instance

        result = mock_brighter_trades.stop_strategy(
            user_id=1,
            strategy_id='test-strategy',
            mode='live'
        )

        assert result['success'] is True
        assert (1, 'test-strategy', 'paper') not in mock_brighter_trades.strategies.active_instances


class TestGetStrategyStatus:
    """Tests for get_strategy_status functionality."""

    @pytest.fixture
    def mock_brighter_trades(self):
        """Create a mock BrighterTrades instance."""
        with patch.dict('sys.modules', {'eventlet': MagicMock()}):
            from BrighterTrades import BrighterTrades

            with patch.object(BrighterTrades, '__init__', lambda x, y: None):
                bt = BrighterTrades(MagicMock())
                bt.strategies = MagicMock()
                bt.strategies.active_instances = {}
                return bt

    def test_get_status_no_running(self, mock_brighter_trades):
        """Test status when no strategies running."""
        result = mock_brighter_trades.get_strategy_status(user_id=1)

        assert result['success'] is True
        assert result['running_strategies'] == []
        assert result['count'] == 0

    def test_get_status_with_running(self, mock_brighter_trades):
        """Test status with running strategies."""
        # Set up running strategies
        mock_instance1 = MagicMock()
        mock_instance1.strategy_name = 'Strategy 1'
        mock_instance1.strategy_instance_id = 'inst-1'
        mock_instance1.broker = MagicMock()
        mock_instance1.broker.get_balance.return_value = 10500.0
        mock_instance1.broker.get_available_balance.return_value = 10000.0
        mock_instance1.broker.get_all_positions.return_value = []
        mock_instance1.trade_history = []

        mock_instance2 = MagicMock()
        mock_instance2.strategy_name = 'Strategy 2'
        mock_instance2.strategy_instance_id = 'inst-2'
        mock_instance2.broker = MagicMock()
        mock_instance2.broker.get_balance.return_value = 9500.0
        mock_instance2.broker.get_available_balance.return_value = 9500.0
        mock_instance2.broker.get_all_positions.return_value = []
        mock_instance2.trade_history = [{'pnl': -500}]

        mock_brighter_trades.strategies.active_instances = {
            (1, 'strat-1', 'paper'): mock_instance1,
            (1, 'strat-2', 'paper'): mock_instance2,
            (2, 'strat-3', 'paper'): MagicMock(),  # Different user
        }

        result = mock_brighter_trades.get_strategy_status(user_id=1)

        assert result['success'] is True
        assert result['count'] == 2  # Only user 1's strategies

        strat_ids = [s['strategy_id'] for s in result['running_strategies']]
        assert 'strat-1' in strat_ids
        assert 'strat-2' in strat_ids
        assert 'strat-3' not in strat_ids  # Different user

    def test_get_status_filter_by_strategy(self, mock_brighter_trades):
        """Test status filtered by specific strategy."""
        mock_instance = MagicMock()
        mock_instance.strategy_name = 'Strategy 1'
        mock_instance.strategy_instance_id = 'inst-1'
        mock_instance.broker = MagicMock()
        mock_instance.broker.get_balance.return_value = 10500.0
        mock_instance.broker.get_available_balance.return_value = 10000.0
        mock_instance.broker.get_all_positions.return_value = []
        mock_instance.trade_history = []

        mock_brighter_trades.strategies.active_instances = {
            (1, 'strat-1', 'paper'): mock_instance,
            (1, 'strat-2', 'paper'): MagicMock(),
        }

        result = mock_brighter_trades.get_strategy_status(
            user_id=1,
            strategy_id='strat-1'
        )

        assert result['count'] == 1
        assert result['running_strategies'][0]['strategy_id'] == 'strat-1'


class TestMessageHandlerNumericParsing:
    """Tests for safe numeric parsing in message handlers."""

    def test_run_strategy_invalid_initial_balance(self):
        """Test that invalid initial_balance is handled gracefully."""
        # This would need integration test with actual message handler
        # For now, verify the structure is correct
        pass

    def test_run_strategy_negative_balance_rejected(self):
        """Test that negative balance is rejected."""
        pass

    def test_run_strategy_invalid_commission_rejected(self):
        """Test that invalid commission is rejected."""
        pass


class TestLiveModeWarning:
    """Tests for live mode fallback messaging."""

    @pytest.fixture
    def mock_brighter_trades(self):
        """Create a mock BrighterTrades instance."""
        with patch.dict('sys.modules', {'eventlet': MagicMock()}):
            from BrighterTrades import BrighterTrades
            import pandas as pd

            with patch.object(BrighterTrades, '__init__', lambda x, y: None):
                bt = BrighterTrades(MagicMock())
                bt.strategies = MagicMock()
                bt.strategies.active_instances = {}
                bt.strategies.data_cache = MagicMock()
                bt.users = MagicMock()
                bt.users.get_username = MagicMock(return_value='test_user')
                bt.get_user_info = MagicMock(
                    side_effect=lambda user_name, info: 1 if info == 'User_id' else 'test_user'
                )
                bt.exchanges = MagicMock()
                bt.exchanges.get_price = MagicMock(return_value=50000.0)
                bt.users.get_exchanges = MagicMock(return_value=['binance'])
                bt.users.get_api_keys = MagicMock(return_value={'key': 'k', 'secret': 's'})
                mock_exchange = MagicMock()
                mock_exchange.testnet = True
                mock_exchange.configured = True
                bt.exchanges.get_exchange = MagicMock(return_value=mock_exchange)
                bt.exchanges.connect_exchange = MagicMock(return_value=True)

                # Mock EDM client for exchange validation
                bt.edm_client = MagicMock()
                bt.edm_client.get_exchanges_sync = MagicMock(return_value=['binance', 'kucoin'])

                # Set up valid strategy
                mock_strategy = pd.DataFrame([{
                    'tbl_key': 'test-strategy',
                    'name': 'Test Strategy',
                    'creator': 'test_user',
                    'public': False,
                    'strategy_components': '{"generated_code": "pass"}'
                }])
                bt.strategies.data_cache.get_rows_from_datacache.return_value = mock_strategy
                bt.strategies.create_strategy_instance = MagicMock()
                bt.strategies.create_strategy_instance.return_value = MagicMock(
                    strategy_name='Test Strategy'
                )

                # Mock strategies.get_strategy_by_tbl_key for exchange validation
                bt.strategies.get_strategy_by_tbl_key = MagicMock(return_value={
                    'strategy_components': {},
                    'default_source': {}
                })

                return bt

    def test_live_mode_returns_success(self, mock_brighter_trades):
        """Test that live mode request succeeds in live mode."""
        result = mock_brighter_trades.start_strategy(
            user_id=1,
            strategy_id='test-strategy',
            mode='live'
        )

        # Should succeed in live mode
        assert result['success'] is True
        assert result['actual_mode'] == 'live'
