class InMemoryExecutionDatabaseTests(unittest.TestCase):
    def setUp(self):
        # Fixture Setup
        clock = TestClock()
        logger = TestLogger()

        self.trader_id = TraderId('TESTER', '000')
        self.account_id = TestStubs.account_id()

        self.strategy = TradingStrategy(order_id_tag='001')
        self.strategy.change_clock(clock)
        self.strategy.change_logger(logger)

        self.database = InMemoryExecutionDatabase(trader_id=self.trader_id,
                                                  logger=logger)

    def test_can_add_order(self):
        # Arrange
        order = self.strategy.order_factory.market(AUDUSD_FXCM, OrderSide.BUY,
                                                   Quantity(100000))
        position_id = self.strategy.position_id_generator.generate()

        # Act
        self.database.add_order(order, self.strategy.id, position_id)

        # Assert
        self.assertTrue(order.id in self.database.get_order_ids())
        self.assertEqual(order, self.database.get_orders()[order.id])

    def test_can_add_position(self):
        # Arrange
        order = self.strategy.order_factory.market(AUDUSD_FXCM, OrderSide.BUY,
                                                   Quantity(100000))
        position_id = self.strategy.position_id_generator.generate()
        self.database.add_order(order, self.strategy.id, position_id)

        order_filled = TestStubs.event_order_filled(order,
                                                    fill_price=Price(
                                                        1.00000, 5))
        position = Position(position_id, order_filled)

        # Act
        self.database.add_position(position, self.strategy.id)

        # Assert
        self.assertTrue(self.database.position_exists_for_order(order.id))
        self.assertTrue(self.database.position_exists(position.id))
        self.assertTrue(position.id in self.database.get_position_ids())
        self.assertTrue(position.id in self.database.get_positions())
        self.assertTrue(
            position.id in self.database.get_positions_open(self.strategy.id))
        self.assertTrue(position.id in self.database.get_positions_open())
        self.assertTrue(position.id not in self.database.get_positions_closed(
            self.strategy.id))
        self.assertTrue(
            position.id not in self.database.get_positions_closed())

    def test_can_update_order_for_working_order(self):
        # Arrange
        order = self.strategy.order_factory.market(AUDUSD_FXCM, OrderSide.BUY,
                                                   Quantity(100000))
        position_id = self.strategy.position_id_generator.generate()
        self.database.add_order(order, self.strategy.id, position_id)

        order_working = TestStubs.event_order_working(order)
        order.apply(order_working)

        # Act
        self.database.update_order(order)

        # Assert
        self.assertTrue(self.database.order_exists(order.id))
        self.assertTrue(order.id in self.database.get_order_ids())
        self.assertTrue(order.id in self.database.get_orders())
        self.assertTrue(
            order.id in self.database.get_orders_working(self.strategy.id))
        self.assertTrue(order.id in self.database.get_orders_working())
        self.assertTrue(order.id not in self.database.get_orders_completed(
            self.strategy.id))
        self.assertTrue(order.id not in self.database.get_orders_completed())

    def test_can_update_order_for_completed_order(self):
        # Arrange
        order = self.strategy.order_factory.market(AUDUSD_FXCM, OrderSide.BUY,
                                                   Quantity(100000))
        position_id = self.strategy.position_id_generator.generate()
        self.database.add_order(order, self.strategy.id, position_id)

        order_filled = TestStubs.event_order_filled(order,
                                                    fill_price=Price(
                                                        1.00001, 5))
        order.apply(order_filled)

        # Act
        self.database.update_order(order)

        # Assert
        self.assertTrue(self.database.order_exists(order.id))
        self.assertTrue(order.id in self.database.get_order_ids())
        self.assertTrue(order.id in self.database.get_orders())
        self.assertTrue(
            order.id in self.database.get_orders_completed(self.strategy.id))
        self.assertTrue(order.id in self.database.get_orders_completed())
        self.assertTrue(
            order.id not in self.database.get_orders_working(self.strategy.id))
        self.assertTrue(order.id not in self.database.get_orders_working())

    def test_can_update_position_for_closed_position(self):
        # Arrange
        order1 = self.strategy.order_factory.market(AUDUSD_FXCM, OrderSide.BUY,
                                                    Quantity(100000))
        position_id = self.strategy.position_id_generator.generate()
        self.database.add_order(order1, self.strategy.id, position_id)

        order1_filled = TestStubs.event_order_filled(order1,
                                                     fill_price=Price(
                                                         1.00001, 5))
        order1.apply(order1_filled)
        position = Position(position_id, order1.last_event)
        self.database.add_position(position, self.strategy.id)

        order2 = self.strategy.order_factory.market(AUDUSD_FXCM,
                                                    OrderSide.SELL,
                                                    Quantity(100000))
        order2_filled = TestStubs.event_order_filled(order2,
                                                     fill_price=Price(
                                                         1.00001, 5))
        position.apply(order2_filled)

        # Act
        self.database.update_position(position)

        # Assert
        self.assertTrue(self.database.position_exists(position.id))
        self.assertTrue(position.id in self.database.get_position_ids())
        self.assertTrue(position.id in self.database.get_positions())
        self.assertTrue(position.id in self.database.get_positions_closed(
            self.strategy.id))
        self.assertTrue(position.id in self.database.get_positions_closed())
        self.assertTrue(position.id not in self.database.get_positions_open(
            self.strategy.id))
        self.assertTrue(position.id not in self.database.get_positions_open())
        self.assertEqual(position,
                         self.database.get_position_for_order(order1.id))

    def test_can_add_account(self):
        # Arrange
        event = AccountStateEvent(
            AccountId.py_from_string('SIMULATED-123456-SIMULATED'),
            Currency.USD, Money(1000000, Currency.USD),
            Money(1000000, Currency.USD), Money(0, Currency.USD),
            Money(0, Currency.USD), Money(0, Currency.USD), Decimal(0),
            ValidString('N'), GUID(uuid.uuid4()), UNIX_EPOCH)

        account = Account(event)

        # Act
        self.database.add_account(account)

        # Assert
        self.assertTrue(True)  # Did not raise exception

    def test_can_update_account(self):
        # Arrange
        event = AccountStateEvent(
            AccountId.py_from_string('SIMULATED-123456-SIMULATED'),
            Currency.USD, Money(1000000, Currency.USD),
            Money(1000000, Currency.USD), Money(0, Currency.USD),
            Money(0, Currency.USD), Money(0, Currency.USD), Decimal(0),
            ValidString('N'), GUID(uuid.uuid4()), UNIX_EPOCH)

        account = Account(event)
        self.database.add_account(account)

        # Act
        self.database.update_account(account)

        # Assert
        self.assertTrue(True)  # Did not raise exception

    def test_can_delete_strategy(self):
        # Arrange
        self.database.update_strategy(self.strategy)

        # Act
        self.database.delete_strategy(self.strategy)

        # Assert
        self.assertTrue(
            self.strategy.id not in self.database.get_strategy_ids())

    def test_can_check_residuals(self):
        # Arrange
        order1 = self.strategy.order_factory.market(AUDUSD_FXCM, OrderSide.BUY,
                                                    Quantity(100000))
        position1_id = self.strategy.position_id_generator.generate()
        self.database.add_order(order1, self.strategy.id, position1_id)

        order1_filled = TestStubs.event_order_filled(order1,
                                                     fill_price=Price(
                                                         1.00000, 5))
        position1 = Position(position1_id, order1_filled)
        self.database.update_order(order1)
        self.database.add_position(position1, self.strategy.id)

        order2 = self.strategy.order_factory.market(AUDUSD_FXCM, OrderSide.BUY,
                                                    Quantity(100000))
        position2_id = self.strategy.position_id_generator.generate()
        self.database.add_order(order2, self.strategy.id, position2_id)
        order2_working = TestStubs.event_order_working(order2)
        order2.apply(order2_working)

        self.database.update_order(order2)

        # Act
        self.database.check_residuals()

        # Does not raise exception

    def test_can_reset(self):
        # Arrange
        order1 = self.strategy.order_factory.market(AUDUSD_FXCM, OrderSide.BUY,
                                                    Quantity(100000))
        position1_id = self.strategy.position_id_generator.generate()
        self.database.add_order(order1, self.strategy.id, position1_id)

        order1_filled = TestStubs.event_order_filled(order1,
                                                     fill_price=Price(
                                                         1.00000, 5))
        position1 = Position(position1_id, order1_filled)
        self.database.update_order(order1)
        self.database.add_position(position1, self.strategy.id)

        order2 = self.strategy.order_factory.market(AUDUSD_FXCM, OrderSide.BUY,
                                                    Quantity(100000))
        position2_id = self.strategy.position_id_generator.generate()
        self.database.add_order(order2, self.strategy.id, position2_id)
        order2_working = TestStubs.event_order_working(order2)
        order2.apply(order2_working)

        self.database.update_order(order2)

        # Act
        self.database.reset()

        # Assert
        self.assertEqual(0, len(self.database.get_strategy_ids()))
        self.assertEqual(0, self.database.count_orders_total())
        self.assertEqual(0, self.database.count_positions_total())

    def test_can_flush(self):
        # Arrange
        order1 = self.strategy.order_factory.market(AUDUSD_FXCM, OrderSide.BUY,
                                                    Quantity(100000))
        position1_id = self.strategy.position_id_generator.generate()
        self.database.add_order(order1, self.strategy.id, position1_id)

        order1_filled = TestStubs.event_order_filled(order1,
                                                     fill_price=Price(
                                                         1.00000, 5))
        position1 = Position(position1_id, order1_filled)
        self.database.update_order(order1)
        self.database.add_position(position1, self.strategy.id)

        order2 = self.strategy.order_factory.market(AUDUSD_FXCM, OrderSide.BUY,
                                                    Quantity(100000))
        position2_id = self.strategy.position_id_generator.generate()
        self.database.add_order(order2, self.strategy.id, position2_id)
        order2_working = TestStubs.event_order_working(order2)
        order2.apply(order2_working)

        self.database.update_order(order2)

        # Act
        self.database.reset()
        self.database.flush()

        # Assert
        # Does not raise exception

    def test_get_strategy_ids_with_no_ids_returns_empty_set(self):
        # Arrange
        # Act
        result = self.database.get_strategy_ids()

        # Assert
        self.assertEqual(set(), result)

    def test_get_strategy_ids_with_id_returns_correct_set(self):
        # Arrange
        self.database.update_strategy(self.strategy)

        # Act
        result = self.database.get_strategy_ids()

        # Assert
        self.assertEqual({self.strategy.id}, result)

    def test_position_exists_when_no_position_returns_false(self):
        # Arrange
        # Act
        # Assert
        self.assertFalse(self.database.position_exists(PositionId('P-123456')))

    def test_order_exists_when_no_order_returns_false(self):
        # Arrange
        # Act
        # Assert
        self.assertFalse(self.database.order_exists(OrderId('O-123456')))

    def test_position_for_order_when_not_found_returns_none(self):
        # Arrange
        # Act
        # Assert
        self.assertIsNone(
            self.database.get_position_for_order(OrderId('O-123456')))

    def test_position_indexed_for_order_when_no_indexing_returns_false(self):
        # Arrange
        # Act
        # Assert
        self.assertFalse(
            self.database.position_indexed_for_order(OrderId('O-123456')))

    def test_get_order_when_no_order_returns_none(self):
        # Arrange
        position_id = PositionId('P-123456')

        # Act
        result = self.database.get_position(position_id)

        # Assert
        self.assertIsNone(result)

    def test_get_position_when_no_position_returns_none(self):
        # Arrange
        order_id = OrderId('O-201908080101-000-001')

        # Act
        result = self.database.get_order(order_id)

        # Assert
        self.assertIsNone(result)