"""
Unit tests for template group exclusivity feature.
Tests that templates in the same group cannot have concurrent active trades.
"""
import unittest
from unittest.mock import Mock, MagicMock, patch
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from optrabot.tradetemplate.templatefactory import Template
from optrabot.tradetemplate.processor.templateprocessorbase import TemplateProcessorBase
from optrabot.managedtrade import ManagedTrade
from optrabot.broker.order import Order
from optrabot.models import Trade
from optrabot.tradestatus import TradeStatus


class TemplateGroupTests(unittest.TestCase):
	"""Test suite for template group exclusivity"""

	def setUp(self):
		"""Set up test fixtures"""
		self.mock_config = self._create_mock_config()
		self.broker_patcher = patch('optrabot.tradetemplate.processor.templateprocessorbase.BrokerFactory')
		self.mock_broker_factory = self.broker_patcher.start()
		broker = Mock()
		self.mock_broker_factory.return_value.getBrokerConnectorByAccount.return_value = broker

	def tearDown(self):
		"""Clean up patches"""
		self.broker_patcher.stop()

	def _create_mock_config(self):
		"""Create mock config with templates in groups"""
		config = Mock()
		
		# Create templates with different group configurations
		template_0dte = Template("0DTEIC100Income")
		template_0dte.template_group = "IC100Income"
		template_0dte.account = "DU6504444"
		
		template_1dte = Template("1DTEIC100Income")
		template_1dte.template_group = "IC100Income"
		template_1dte.account = "DU6504444"
		
		template_final = Template("FINALIC100Income")
		template_final.template_group = "IC100Income"
		template_final.account = "DU6504444"
		
		# Template without group
		template_independent = Template("0DTEMagickTrend10")
		template_independent.template_group = None
		template_independent.account = "DU6504444"
		
		# Template in different group
		template_other_group = Template("0DTEIronFly")
		template_other_group.template_group = "IronFly"
		template_other_group.account = "DU6504444"
		
		config.getTemplates.return_value = [
			template_0dte,
			template_1dte,
			template_final,
			template_independent,
			template_other_group
		]
		
		return config

	def _create_managed_trade(self, template_name: str, is_active: bool = True):
		"""Helper to create a ManagedTrade for testing"""
		template = Template(template_name)
		template.account = "DU6504444"
		
		trade = Mock(spec=Trade)
		entry_order = Mock(spec=Order)
		
		managed_trade = ManagedTrade(trade, template, entry_order, "DU6504444")
		managed_trade.status = TradeStatus.OPEN if is_active else TradeStatus.CLOSED
		
		return managed_trade

	def test_template_group_field_initialization(self):
		"""Test that template_group field is properly initialized"""
		template = Template("TestTemplate")
		self.assertTrue(hasattr(template, 'template_group'))
		self.assertIsNone(template.template_group)

	def test_template_group_config_parsing(self):
		"""Test that template_group is correctly parsed from config"""
		templates = self.mock_config.getTemplates()
		
		# Check IC100Income group
		ic_templates = [t for t in templates if t.template_group == "IC100Income"]
		self.assertEqual(len(ic_templates), 3)
		self.assertTrue(any(t.name == "0DTEIC100Income" for t in ic_templates))
		self.assertTrue(any(t.name == "1DTEIC100Income" for t in ic_templates))
		self.assertTrue(any(t.name == "FINALIC100Income" for t in ic_templates))
		
		# Check independent template
		independent = [t for t in templates if t.name == "0DTEMagickTrend10"][0]
		self.assertIsNone(independent.template_group)

	@patch('optrabot.trademanager.TradeManager')
	def test_no_active_trades_in_group(self, mock_trade_manager):
		"""Test that check_conditions passes when no trades are active in group"""
		# Setup
		mock_trade_manager.return_value.getManagedTrades.return_value = []
		
		template = Template("0DTEIC100Income")
		template.template_group = "IC100Income"
		template.account = "DU6504444"
		
		with patch('optrabot.config.appConfig', self.mock_config):
			processor = TemplateProcessorBase(template)
			result = processor.check_conditions()
		
		self.assertTrue(result)

	@patch('optrabot.trademanager.TradeManager')
	def test_active_trade_in_same_group_blocks(self, mock_trade_manager):
		"""Test that active trade in same group blocks new template"""
		# Setup: 1DTEIC100Income trade is active
		active_trade = self._create_managed_trade("1DTEIC100Income", is_active=True)
		mock_trade_manager.return_value.getManagedTrades.return_value = [active_trade]
		
		# Try to process 0DTEIC100Income (same group)
		template = Template("0DTEIC100Income")
		template.template_group = "IC100Income"
		template.account = "DU6504444"
		
		with patch('optrabot.config.appConfig', self.mock_config):
			processor = TemplateProcessorBase(template)
			result = processor.check_conditions()
		
		self.assertFalse(result)

	@patch('optrabot.trademanager.TradeManager')
	def test_closed_trade_in_group_does_not_block(self, mock_trade_manager):
		"""Test that closed trades don't block new templates"""
		# Setup: Trade is closed
		closed_trade = self._create_managed_trade("1DTEIC100Income", is_active=False)
		mock_trade_manager.return_value.getManagedTrades.return_value = [closed_trade]
		
		template = Template("0DTEIC100Income")
		template.template_group = "IC100Income"
		template.account = "DU6504444"
		
		with patch('optrabot.config.appConfig', self.mock_config):
			processor = TemplateProcessorBase(template)
			result = processor.check_conditions()
		
		self.assertTrue(result)

	@patch('optrabot.trademanager.TradeManager')
	def test_own_active_trade_blocks_duplicate(self, mock_trade_manager):
		"""Test that a template blocks duplicate trades (same template already has active trade)"""
		# Setup: 0DTEIC100Income trade is active
		active_trade = self._create_managed_trade("0DTEIC100Income", is_active=True)
		mock_trade_manager.return_value.getManagedTrades.return_value = [active_trade]
		
		# Same template tries to process again (should be blocked!)
		template = Template("0DTEIC100Income")
		template.template_group = "IC100Income"
		template.account = "DU6504444"
		
		with patch('optrabot.config.appConfig', self.mock_config):
			processor = TemplateProcessorBase(template)
			result = processor.check_conditions()
		
		# Should be blocked because same template already has active trade
		self.assertFalse(result)

	@patch('optrabot.trademanager.TradeManager')
	def test_different_group_does_not_block(self, mock_trade_manager):
		"""Test that active trade in different group doesn't block"""
		# Setup: IronFly template is active
		active_trade = self._create_managed_trade("0DTEIronFly", is_active=True)
		active_trade.template.template_group = "IronFly"
		mock_trade_manager.return_value.getManagedTrades.return_value = [active_trade]
		
		# Try to process IC100Income (different group)
		template = Template("0DTEIC100Income")
		template.template_group = "IC100Income"
		template.account = "DU6504444"
		
		with patch('optrabot.config.appConfig', self.mock_config):
			processor = TemplateProcessorBase(template)
			result = processor.check_conditions()
		
		self.assertTrue(result)

	@patch('optrabot.trademanager.TradeManager')
	def test_template_without_group_not_affected(self, mock_trade_manager):
		"""Test that templates without group are not affected by group logic"""
		# Setup: Some trade is active
		active_trade = self._create_managed_trade("0DTEIC100Income", is_active=True)
		mock_trade_manager.return_value.getManagedTrades.return_value = [active_trade]
		
		# Template without group should not be blocked
		template = Template("0DTEMagickTrend10")
		template.template_group = None  # No group
		template.account = "DU6504444"
		
		with patch('optrabot.config.appConfig', self.mock_config):
			processor = TemplateProcessorBase(template)
			result = processor.check_conditions()
		
		# Should pass because template has no group
		self.assertTrue(result)

	@patch('optrabot.trademanager.TradeManager')
	def test_multiple_active_trades_in_group(self, mock_trade_manager):
		"""Test blocking with multiple active trades in same group"""
		# Setup: Both 1DTE and FINAL are active (edge case, but test it)
		trade1 = self._create_managed_trade("1DTEIC100Income", is_active=True)
		trade2 = self._create_managed_trade("FINALIC100Income", is_active=True)
		mock_trade_manager.return_value.getManagedTrades.return_value = [trade1, trade2]
		
		template = Template("0DTEIC100Income")
		template.template_group = "IC100Income"
		template.account = "DU6504444"
		
		with patch('optrabot.config.appConfig', self.mock_config):
			processor = TemplateProcessorBase(template)
			result = processor.check_conditions()
		
		self.assertFalse(result)

	@patch('optrabot.trademanager.TradeManager')
	def test_group_check_before_vix_check(self, mock_trade_manager):
		"""Test that group check happens before VIX check (optimization)"""
		# Setup: Active trade in group
		active_trade = self._create_managed_trade("1DTEIC100Income", is_active=True)
		mock_trade_manager.return_value.getManagedTrades.return_value = [active_trade]
		
		template = Template("0DTEIC100Income")
		template.template_group = "IC100Income"
		template.account = "DU6504444"
		template.vix_max = 25  # VIX condition set
		
		with patch('optrabot.config.appConfig', self.mock_config):
			processor = TemplateProcessorBase(template)
			
			# Mock broker to track if getLastPrice is called
			mock_broker = Mock()
			with patch('optrabot.tradetemplate.processor.templateprocessorbase.BrokerFactory') as bf:
				bf.return_value.getBrokerConnectorByAccount.return_value = mock_broker
				result = processor.check_conditions()
		
		# Should return False due to group check
		self.assertFalse(result)
		# VIX should NOT have been checked (optimization)
		mock_broker.getLastPrice.assert_not_called()

	@patch('optrabot.trademanager.TradeManager')
	def test_rollover_scenario(self, mock_trade_manager):
		"""Test typical rollover scenario: 0DTE -> 1DTE -> FINAL"""
		
		# Scenario 1: 0DTE signal arrives, no trades active -> ALLOW
		mock_trade_manager.return_value.getManagedTrades.return_value = []
		template = Template("0DTEIC100Income")
		template.template_group = "IC100Income"
		template.account = "DU6504444"
		
		with patch('optrabot.config.appConfig', self.mock_config):
			processor = TemplateProcessorBase(template)
			self.assertTrue(processor.check_conditions())
		
		# Scenario 2: 0DTE exits, 1DTE signal arrives while 0DTE still "OPEN" -> BLOCK
		# (In reality, early_exit closes the trade, but let's test the edge case)
		active_0dte = self._create_managed_trade("0DTEIC100Income", is_active=True)
		mock_trade_manager.return_value.getManagedTrades.return_value = [active_0dte]
		
		template_1dte = Template("1DTEIC100Income")
		template_1dte.template_group = "IC100Income"
		template_1dte.account = "DU6504444"
		
		with patch('optrabot.config.appConfig', self.mock_config):
			processor_1dte = TemplateProcessorBase(template_1dte)
			self.assertFalse(processor_1dte.check_conditions())
		
		# Scenario 3: 0DTE closed, 1DTE now active, new 0DTE signal -> BLOCK
		active_1dte = self._create_managed_trade("1DTEIC100Income", is_active=True)
		mock_trade_manager.return_value.getManagedTrades.return_value = [active_1dte]
		
		template_0dte_new = Template("0DTEIC100Income")
		template_0dte_new.template_group = "IC100Income"
		template_0dte_new.account = "DU6504444"
		
		with patch('optrabot.config.appConfig', self.mock_config):
			processor_0dte_new = TemplateProcessorBase(template_0dte_new)
			self.assertFalse(processor_0dte_new.check_conditions())


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

	"""Test suite for template group exclusivity"""

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