"""
Unit tests for OTB-253 Phase 2: Active Trade Recovery

Tests cover:
- Template lookup by name
- Entry order reconstruction from transactions
- ManagedTrade creation
- Trade recovery workflow
- Error handling scenarios
"""

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

import unittest
from datetime import date, datetime, timedelta
from unittest.mock import Mock, MagicMock, patch
from sqlalchemy.orm import Session

from optrabot.traderecovery import TradeRecoveryService
from optrabot.models import Trade, Transaction
from optrabot.tradestatus import TradeStatus
from optrabot.tradetemplate.templatefactory import Template, IronCondor
from optrabot.broker.order import Order, Leg, OrderAction, OptionRight, OrderStatus, PriceEffect
from optrabot.managedtrade import ManagedTrade
from optrabot.config import Config

from testtools import get_test_db_engine, TestSessionLocal


class TradeRecoveryConfigTests(unittest.TestCase):
	"""Tests for Config.get_template_by_name()"""
	
	def setUp(self):
		"""Create a mock config with test templates"""
		self.config = Config.__new__(Config)
		self.config._templates = []
		
		# Create test templates
		template1 = Mock(spec=Template)
		template1.name = "IC_SPX_0DTE"
		template1.strategy = "Iron Condor"
		
		template2 = Mock(spec=Template)
		template2.name = "IC_SPX_1DTE"
		template2.strategy = "Iron Condor"
		
		template3 = Mock(spec=Template)
		template3.name = "PutSpread_SPX"
		template3.strategy = "Put Spread"
		
		self.config._templates = [template1, template2, template3]
	
	def test_get_template_by_name_found(self):
		"""Test successful template lookup"""
		template = self.config.get_template_by_name("IC_SPX_0DTE")
		self.assertIsNotNone(template)
		self.assertEqual(template.name, "IC_SPX_0DTE")
		self.assertEqual(template.strategy, "Iron Condor")
	
	def test_get_template_by_name_not_found(self):
		"""Test template lookup with non-existent name"""
		template = self.config.get_template_by_name("NonExistent")
		self.assertIsNone(template)
	
	def test_get_template_by_name_empty_string(self):
		"""Test template lookup with empty string"""
		template = self.config.get_template_by_name("")
		self.assertIsNone(template)
	
	def test_get_template_by_name_none(self):
		"""Test template lookup with None"""
		template = self.config.get_template_by_name(None)
		self.assertIsNone(template)
	
	def test_get_template_by_name_case_sensitive(self):
		"""Test that template lookup is case-sensitive"""
		template = self.config.get_template_by_name("ic_spx_0dte")
		self.assertIsNone(template)


class TradeRecoveryOrderReconstructionTests(unittest.TestCase):
	"""Tests for _reconstruct_entry_order()"""
	
	def setUp(self):
		"""Create TradeRecoveryService instance and mock template"""
		self.recovery_service = TradeRecoveryService()
		
		# Create a mock template for testing
		self.mock_template = Mock(spec=Template)
		self.mock_template.name = "IC_SPX_0DTE"
		self.mock_template.strategy = "Iron Condor"
		self.mock_template.amount = 1  # Standard quantity
	
	def _create_test_trade_with_transactions(self, transactions_data):
		"""Helper to create a Trade with given transactions"""
		trade = Trade()
		trade.id = 1
		trade.account = "DU123456"
		trade.symbol = "SPX"
		trade.strategy = "Iron Condor"
		trade.status = TradeStatus.OPEN
		trade.transactions = []
		
		for tx_data in transactions_data:
			transaction = Transaction()
			transaction.tradeid = trade.id
			transaction.id = tx_data.get('id', 0)
			transaction.type = tx_data['type']
			transaction.sectype = tx_data['sectype']
			transaction.strike = tx_data['strike']
			transaction.expiration = tx_data.get('expiration', date.today() + timedelta(days=1))
			transaction.contracts = tx_data['contracts']
			transaction.price = tx_data['price']
			transaction.timestamp = tx_data.get('timestamp', datetime.now())
			trade.transactions.append(transaction)
		
		return trade
	
	def test_reconstruct_credit_spread(self):
		"""Test reconstructing a credit spread (SELL higher premium than BUY)"""
		transactions = [
			{'id': 1, 'type': 'Sell', 'sectype': 'P', 'strike': 5900, 'contracts': 1, 'price': 10.0},
			{'id': 2, 'type': 'Buy', 'sectype': 'P', 'strike': 5850, 'contracts': 1, 'price': 5.0}
		]
		trade = self._create_test_trade_with_transactions(transactions)
		
		order = self.recovery_service._reconstruct_entry_order(trade, self.mock_template)
		
		# Verify order properties
		self.assertEqual(order.symbol, "SPX")
		self.assertEqual(len(order.legs), 2)
		self.assertEqual(order.status, OrderStatus.FILLED)
		self.assertEqual(order.price_effect, PriceEffect.CREDIT)
		self.assertEqual(order.filled_price, 5.0)  # 10.0 - 5.0
		self.assertEqual(order.action, OrderAction.BUY_TO_OPEN)  # Entry orders are BUY_TO_OPEN
		self.assertEqual(order.quantity, 1)  # From template.amount
		
		# Verify legs
		sell_leg = order.legs[0]
		self.assertEqual(sell_leg.action, OrderAction.SELL)
		self.assertEqual(sell_leg.right, OptionRight.PUT)
		self.assertEqual(sell_leg.strike, 5900)
		self.assertEqual(sell_leg.quantity, 1)
		
		buy_leg = order.legs[1]
		self.assertEqual(buy_leg.action, OrderAction.BUY)
		self.assertEqual(buy_leg.right, OptionRight.PUT)
		self.assertEqual(buy_leg.strike, 5850)
		self.assertEqual(buy_leg.quantity, 1)
	
	def test_reconstruct_debit_spread(self):
		"""Test reconstructing a debit spread (BUY costs more than SELL receives)"""
		transactions = [
			{'id': 1, 'type': 'Buy', 'sectype': 'P', 'strike': 5900, 'contracts': 1, 'price': 10.0},
			{'id': 2, 'type': 'Sell', 'sectype': 'P', 'strike': 5850, 'contracts': 1, 'price': 5.0}
		]
		trade = self._create_test_trade_with_transactions(transactions)
		
		order = self.recovery_service._reconstruct_entry_order(trade, self.mock_template)
		
		self.assertEqual(order.price_effect, PriceEffect.DEBIT)
		self.assertEqual(order.filled_price, 5.0)  # abs(-10.0 + 5.0)
		self.assertEqual(order.action, OrderAction.BUY_TO_OPEN)
		self.assertEqual(order.quantity, 1)
	
	def test_reconstruct_iron_condor(self):
		"""Test reconstructing an Iron Condor with 4 legs"""
		transactions = [
			# Put spread
			{'id': 1, 'type': 'Sell', 'sectype': 'P', 'strike': 5900, 'contracts': 1, 'price': 10.0},
			{'id': 2, 'type': 'Buy', 'sectype': 'P', 'strike': 5850, 'contracts': 1, 'price': 5.0},
			# Call spread
			{'id': 3, 'type': 'Sell', 'sectype': 'C', 'strike': 6100, 'contracts': 1, 'price': 10.0},
			{'id': 4, 'type': 'Buy', 'sectype': 'C', 'strike': 6150, 'contracts': 1, 'price': 5.0}
		]
		trade = self._create_test_trade_with_transactions(transactions)
		
		order = self.recovery_service._reconstruct_entry_order(trade, self.mock_template)
		
		self.assertEqual(len(order.legs), 4)
		self.assertEqual(order.filled_price, 10.0)  # (10-5) + (10-5)
		
		# Verify put legs
		put_sells = [leg for leg in order.legs if leg.right == OptionRight.PUT and leg.action == OrderAction.SELL]
		put_buys = [leg for leg in order.legs if leg.right == OptionRight.PUT and leg.action == OrderAction.BUY]
		self.assertEqual(len(put_sells), 1)
		self.assertEqual(len(put_buys), 1)
		
		# Verify call legs
		call_sells = [leg for leg in order.legs if leg.right == OptionRight.CALL and leg.action == OrderAction.SELL]
		call_buys = [leg for leg in order.legs if leg.right == OptionRight.CALL and leg.action == OrderAction.BUY]
		self.assertEqual(len(call_sells), 1)
		self.assertEqual(len(call_buys), 1)
	
	def test_reconstruct_ignores_closing_transactions(self):
		"""Test that closing transactions (negative contracts) are ignored"""
		transactions = [
			# Opening transactions
			{'id': 1, 'type': 'Sell', 'sectype': 'P', 'strike': 5900, 'contracts': 1, 'price': 10.0},
			{'id': 2, 'type': 'Buy', 'sectype': 'P', 'strike': 5850, 'contracts': 1, 'price': 5.0},
			# Closing transactions (would have contracts=0 or be separate closing order)
			# In our system, closing is tracked via separate mechanism
		]
		trade = self._create_test_trade_with_transactions(transactions)
		
		order = self.recovery_service._reconstruct_entry_order(trade, self.mock_template)
		
		# Only opening transactions should be in the order
		self.assertEqual(len(order.legs), 2)
		self.assertEqual(order.action, OrderAction.BUY_TO_OPEN)
		self.assertEqual(order.quantity, 1)
	
	def test_reconstruct_multi_contract_trade(self):
		"""Test reconstructing a trade with amount=2 (2 contracts per leg)
		
		This tests the scenario where a trade is opened with multiple contracts per leg.
		The recovery should:
		1. Normalize all legs to quantity=1
		2. Calculate amount from transaction.contracts (max of all opening transactions)
		3. Calculate price_per_unit = total_premium / amount
		"""
		transactions = [
			# Put spread with 2 contracts per leg
			{'id': 1, 'type': 'Sell', 'sectype': 'P', 'strike': 5900, 'contracts': 2, 'price': 10.0},
			{'id': 2, 'type': 'Buy', 'sectype': 'P', 'strike': 5850, 'contracts': 2, 'price': 5.0},
			# Call spread with 2 contracts per leg
			{'id': 3, 'type': 'Sell', 'sectype': 'C', 'strike': 6100, 'contracts': 2, 'price': 10.0},
			{'id': 4, 'type': 'Buy', 'sectype': 'C', 'strike': 6150, 'contracts': 2, 'price': 5.0}
		]
		trade = self._create_test_trade_with_transactions(transactions)
		
		order = self.recovery_service._reconstruct_entry_order(trade, self.mock_template)
		
		# Verify amount is calculated from transaction.contracts
		self.assertEqual(order.quantity, 2, "Order quantity should be 2 (amount from transactions)")
		
		# Verify all legs are normalized to quantity=1
		for leg in order.legs:
			self.assertEqual(leg.quantity, 1, "All legs should be normalized to quantity=1")
		
		# Verify price per unit calculation
		# Total premium: (10-5)*2 + (10-5)*2 = 20
		# Price per unit: 20 / 2 = 10
		self.assertEqual(order.filled_price, 10.0, "Filled price should be 10.0 (premium per unit)")
		self.assertEqual(order.price, 10.0, "Order price should be 10.0 (premium per unit)")
		self.assertEqual(order.averageFillPrice, 10.0, "Average fill price should be 10.0")
		
		# Verify other properties
		self.assertEqual(len(order.legs), 4)
		self.assertEqual(order.action, OrderAction.BUY_TO_OPEN)
		self.assertEqual(order.price_effect, PriceEffect.CREDIT)
	
	def test_reconstruct_multi_contract_credit_spread(self):
		"""Test reconstructing a credit spread with amount=3"""
		transactions = [
			{'id': 1, 'type': 'Sell', 'sectype': 'P', 'strike': 5900, 'contracts': 3, 'price': 10.0},
			{'id': 2, 'type': 'Buy', 'sectype': 'P', 'strike': 5850, 'contracts': 3, 'price': 5.0}
		]
		trade = self._create_test_trade_with_transactions(transactions)
		
		order = self.recovery_service._reconstruct_entry_order(trade, self.mock_template)
		
		# Verify amount
		self.assertEqual(order.quantity, 3, "Order quantity should be 3")
		
		# Verify legs normalized
		self.assertEqual(order.legs[0].quantity, 1)
		self.assertEqual(order.legs[1].quantity, 1)
		
		# Verify price per unit: (10-5)*3 / 3 = 5.0
		self.assertEqual(order.filled_price, 5.0)
		self.assertEqual(order.price, 5.0)
		self.assertEqual(order.averageFillPrice, 5.0)
	
	def test_reconstruct_partial_fill_iron_condor(self):
		"""Test reconstructing an Iron Condor with partial fills
		
		This tests the scenario where an order for 20 contracts was filled in two parts:
		- First fill: 18 contracts
		- Second fill: 2 contracts
		
		The recovery should:
		1. Group transactions by leg (strike, sectype, expiration, action)
		2. Sum the contracts for each leg (18 + 2 = 20)
		3. Calculate total premium correctly
		4. Normalize all legs to quantity=1 with amount=20
		"""
		transactions = [
			# First partial fill: 18 contracts
			{'id': 1, 'type': 'Buy', 'sectype': 'C', 'strike': 7080.0, 'contracts': 18, 'price': 0.6},
			{'id': 2, 'type': 'Sell', 'sectype': 'C', 'strike': 7065.0, 'contracts': 18, 'price': 0.75},
			{'id': 3, 'type': 'Buy', 'sectype': 'P', 'strike': 6705.0, 'contracts': 18, 'price': 2.05},
			{'id': 4, 'type': 'Sell', 'sectype': 'P', 'strike': 6720.0, 'contracts': 18, 'price': 2.35},
			# Second partial fill: 2 contracts
			{'id': 5, 'type': 'Buy', 'sectype': 'C', 'strike': 7080.0, 'contracts': 2, 'price': 0.6},
			{'id': 6, 'type': 'Sell', 'sectype': 'C', 'strike': 7065.0, 'contracts': 2, 'price': 0.75},
			{'id': 7, 'type': 'Buy', 'sectype': 'P', 'strike': 6705.0, 'contracts': 2, 'price': 2.05},
			{'id': 8, 'type': 'Sell', 'sectype': 'P', 'strike': 6720.0, 'contracts': 2, 'price': 2.345}
		]
		trade = self._create_test_trade_with_transactions(transactions)
		
		order = self.recovery_service._reconstruct_entry_order(trade, self.mock_template)
		
		# Verify amount is calculated from summed contracts (18 + 2 = 20)
		self.assertEqual(order.quantity, 20, "Order quantity should be 20 (18 + 2 contracts)")
		
		# Verify we have exactly 4 unique legs (not 8)
		self.assertEqual(len(order.legs), 4, "Should have exactly 4 legs (grouped from 8 transactions)")
		
		# Verify all legs are normalized to quantity=1
		for leg in order.legs:
			self.assertEqual(leg.quantity, 1, "All legs should be normalized to quantity=1")
		
		# Verify total premium calculation
		# Call spread: (0.75 - 0.6) * 20 = 3.0
		# Put spread: (2.35 - 2.05) * 18 + (2.345 - 2.05) * 2 = 5.4 + 0.59 = 5.99
		# Total: 3.0 + 5.99 = 8.99
		# Price per unit: 8.99 / 20 = 0.4495
		expected_premium = ((0.75 - 0.6) * 18 + (0.75 - 0.6) * 2) + ((2.35 - 2.05) * 18 + (2.345 - 2.05) * 2)
		expected_price_per_unit = expected_premium / 20
		self.assertAlmostEqual(order.filled_price, expected_price_per_unit, places=4)
		self.assertAlmostEqual(order.price, expected_price_per_unit, places=4)
		
		# Verify other properties
		self.assertEqual(order.action, OrderAction.BUY_TO_OPEN)
		self.assertEqual(order.price_effect, PriceEffect.CREDIT)
		self.assertEqual(order.status, OrderStatus.FILLED)
		
		# Verify leg strikes are correct
		strikes = sorted([leg.strike for leg in order.legs])
		self.assertEqual(strikes, [6705.0, 6720.0, 7065.0, 7080.0])


class TradeRecoveryManagedTradeCreationTests(unittest.TestCase):
	"""Tests for _create_managed_trade()"""
	
	def setUp(self):
		"""Create TradeRecoveryService instance and mock objects"""
		self.recovery_service = TradeRecoveryService()
		
		# Create mock template
		self.mock_template = Mock(spec=Template)
		self.mock_template.name = "IC_SPX_0DTE"
		self.mock_template.strategy = "Iron Condor"
		self.mock_template.account = "DU123456"
		self.mock_template.get_stoploss_adjusters = Mock(return_value=[])
		self.mock_template.get_delta_adjusters = Mock(return_value=[])
		
		# Create mock trade
		self.mock_trade = Mock(spec=Trade)
		self.mock_trade.id = 1
		self.mock_trade.account = "DU123456"
		self.mock_trade.symbol = "SPX"
		self.mock_trade.strategy = "Iron Condor"
		self.mock_trade.status = TradeStatus.OPEN
		
		# Create mock entry order
		self.mock_order = Mock(spec=Order)
		self.mock_order.symbol = "SPX"
		self.mock_order.filled_price = 1.50
		self.mock_order.legs = [
			Mock(action=OrderAction.SELL, strike=5900, right=OptionRight.PUT, quantity=1),
			Mock(action=OrderAction.BUY, strike=5850, right=OptionRight.PUT, quantity=1)
		]
	
	def test_create_managed_trade_basic_properties(self):
		"""Test that ManagedTrade is created with correct basic properties"""
		managed_trade = self.recovery_service._create_managed_trade(
			self.mock_trade,
			self.mock_template,
			self.mock_order
		)
		
		self.assertIsInstance(managed_trade, ManagedTrade)
		self.assertEqual(managed_trade.trade, self.mock_trade)
		self.assertEqual(managed_trade.template, self.mock_template)
		self.assertEqual(managed_trade.entryOrder, self.mock_order)
		self.assertEqual(managed_trade.account, "DU123456")
	
	def test_create_managed_trade_status_open(self):
		"""Test that recovered trade has status OPEN"""
		managed_trade = self.recovery_service._create_managed_trade(
			self.mock_trade,
			self.mock_template,
			self.mock_order
		)
		
		self.assertEqual(managed_trade.status, TradeStatus.OPEN)
	
	def test_create_managed_trade_entry_price(self):
		"""Test that entry_price is set from filled order"""
		managed_trade = self.recovery_service._create_managed_trade(
			self.mock_trade,
			self.mock_template,
			self.mock_order
		)
		
		self.assertEqual(managed_trade.entry_price, 1.50)
	
	def test_create_managed_trade_current_legs(self):
		"""Test that current_legs are copied from entry order"""
		managed_trade = self.recovery_service._create_managed_trade(
			self.mock_trade,
			self.mock_template,
			self.mock_order
		)
		
		self.assertEqual(len(managed_trade.current_legs), 2)
		# Verify it's a deep copy (not the same objects)
		self.assertIsNot(managed_trade.current_legs, self.mock_order.legs)
	
	def test_create_managed_trade_adjusters_setup(self):
		"""Test that adjusters are set up (methods called)"""
		# This test verifies that setup methods are called
		# The actual setup logic is tested in ManagedTrade's own tests
		managed_trade = self.recovery_service._create_managed_trade(
			self.mock_trade,
			self.mock_template,
			self.mock_order
		)
		
		# Verify template methods were called
		self.mock_template.get_stoploss_adjusters.assert_called_once()
		self.mock_template.get_delta_adjusters.assert_called_once()


class TradeRecoveryIntegrationTests(unittest.TestCase):
	"""Integration tests for the complete recovery workflow"""
	
	@patch('optrabot.broker.brokerfactory.BrokerFactory')
	@patch('optrabot.trademanager.TradeManager')
	@patch('optrabot.config.appConfig')
	def test_recover_single_trade_success(self, mock_config, mock_trade_manager_class, mock_broker_factory_class):
		"""Test successful recovery of a single trade"""
		# Setup mocks
		recovery_service = TradeRecoveryService()
		
		# Mock template
		mock_template = Mock(spec=Template)
		mock_template.name = "IC_SPX_0DTE"
		mock_template.strategy = "Iron Condor"
		mock_template.account = "DU123456"
		mock_template.amount = 1  # Add amount for entry order quantity
		mock_template.get_stoploss_adjusters = Mock(return_value=[])
		mock_template.get_delta_adjusters = Mock(return_value=[])
		
		mock_config.get_template_by_name = Mock(return_value=mock_template)
		
		# Mock broker connector and BrokerFactory
		mock_broker = Mock()
		async def mock_prepare_order(order, need_valid_price_data=True):
			# Simulate filling broker-specific data
			order.brokerSpecific['comboLegs'] = []
			order.brokerSpecific['comboContracts'] = []
			order.brokerSpecific['trade'] = Mock()
		
		mock_broker.prepareOrder = mock_prepare_order
		
		mock_broker_factory = Mock()
		mock_broker_factory.getBrokerConnectorByAccount = Mock(return_value=mock_broker)
		mock_broker_factory_class.return_value = mock_broker_factory
		
		# Mock TradeManager
		mock_tm = Mock()
		mock_tm._trades = []
		mock_trade_manager_class.return_value = mock_tm
		
		# Create test trade with transactions
		with patch('optrabot.traderecovery.Session') as mock_session_class:
			mock_session = MagicMock()
			mock_session_class.return_value.__enter__ = Mock(return_value=mock_session)
			mock_session_class.return_value.__exit__ = Mock(return_value=False)
			
			# Mock trade
			mock_trade = Mock(spec=Trade)
			mock_trade.id = 1
			mock_trade.account = "DU123456"
			mock_trade.symbol = "SPX"
			mock_trade.strategy = "Iron Condor"
			
			# Mock transactions
			tx1 = Mock(spec=Transaction)
			tx1.type = 'SELL'
			tx1.sectype = 'P'
			tx1.strike = 5900
			tx1.expiration = date.today() + timedelta(days=1)
			tx1.contracts = 1
			tx1.price = 10.0
			
			tx2 = Mock(spec=Transaction)
			tx2.type = 'BUY'
			tx2.sectype = 'P'
			tx2.strike = 5850
			tx2.expiration = date.today() + timedelta(days=1)
			tx2.contracts = 1
			tx2.price = 5.0
			
			mock_trade.transactions = [tx1, tx2]
			
			with patch('optrabot.traderecovery.crud') as mock_crud:
				mock_crud.getTrade = Mock(return_value=mock_trade)
				
				# Run recovery - this should NOT raise an exception
				try:
					import asyncio
					asyncio.run(recovery_service._recover_single_trade(1, "IC_SPX_0DTE"))
					success = True
				except Exception as e:
					success = False
					print(f"Recovery failed: {e}")
				
				self.assertTrue(success, "Trade recovery should succeed")
				
				# Verify template was loaded
				mock_config.get_template_by_name.assert_called_once_with("IC_SPX_0DTE")
				
				# Verify trade was retrieved
				mock_crud.getTrade.assert_called_once_with(mock_session, 1)
				
				# Verify trade was added to TradeManager
				self.assertEqual(len(mock_tm._trades), 1)
	
	@patch('optrabot.broker.brokerfactory.BrokerFactory')
	@patch('optrabot.config.appConfig')
	def test_recover_single_trade_template_not_found(self, mock_config, mock_broker_factory_class):
		"""Test recovery fails gracefully when template not found"""
		recovery_service = TradeRecoveryService()
		
		# Mock: template not found
		mock_config.get_template_by_name = Mock(return_value=None)
		
		# Should raise exception
		with self.assertRaises(Exception) as context:
			import asyncio
			asyncio.run(recovery_service._recover_single_trade(1, "NonExistent"))
		
		self.assertIn("Template 'NonExistent' not found", str(context.exception))
	
	@patch('optrabot.broker.brokerfactory.BrokerFactory')
	@patch('optrabot.config.appConfig')
	def test_recover_single_trade_no_transactions(self, mock_config, mock_broker_factory_class):
		"""Test recovery fails when trade has no transactions"""
		recovery_service = TradeRecoveryService()
		
		# Mock template
		mock_template = Mock(spec=Template)
		mock_config.get_template_by_name = Mock(return_value=mock_template)
		
		with patch('optrabot.traderecovery.Session') as mock_session_class:
			mock_session = MagicMock()
			mock_session_class.return_value.__enter__ = Mock(return_value=mock_session)
			mock_session_class.return_value.__exit__ = Mock(return_value=False)
			
			# Mock trade without transactions
			mock_trade = Mock(spec=Trade)
			mock_trade.id = 1
			mock_trade.transactions = []
			
			with patch('optrabot.traderecovery.crud') as mock_crud:
				mock_crud.getTrade = Mock(return_value=mock_trade)
				
				# Should raise exception
				with self.assertRaises(Exception) as context:
					import asyncio
					asyncio.run(recovery_service._recover_single_trade(1, "IC_SPX_0DTE"))
				
				self.assertIn("has no transactions", str(context.exception))


class TradeRecoveryCounterTests(unittest.TestCase):
	"""Tests for recovery counters"""
	
	def test_initial_counters(self):
		"""Test that counters are initialized to 0"""
		service = TradeRecoveryService()
		self.assertEqual(service.recovered_count, 0)
		self.assertEqual(service.settled_count, 0)
		self.assertEqual(service.failed_count, 0)


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