import unittest
from decimal import Decimal
from exceptions import MismatchedTradeError, TradeNotFound
from ledger_action import LedgerAction
from ledger_process import LedgerProcess


class TestLedgerProcess(unittest.TestCase):
    def setUp(self):
        """Set up a LedgerProcess instance for testing."""
        self.lp = LedgerProcess()

    def test_process_trade_valid(self):
        """Test valid processing of a trade with both EUR and crypto assets."""
        eur_trade = LedgerAction(
            type="trade",
            asset="EUR",
            amount=Decimal("-500.00"),
            fee=Decimal("2.00"),
            timestamp="2025-04-17 10:00:00",
            refid="12345",
        )
        crypto_trade = LedgerAction(
            type="trade",
            asset="BTC",
            amount=Decimal("0.1"),
            fee=Decimal("0.001"),
            timestamp="2025-04-17 10:00:00",
            refid="12345",
        )
        self.lp.process_ledger([eur_trade, crypto_trade])
        # Assert the remaining balance in the FIFO queue
        self.assertEqual(
            self.lp.fifo_queues["BTC"].get_remaining_amount(), Decimal("0.099")
        )

    def test_empty_actions(self):
        """Test processing with no actions."""
        self.lp.process_ledger([])
        # with self.assertRaises(ValueError):

    def test_missing_trade_rows(self):
        """Test processing a trade with missing EUR or crypto rows."""
        crypto_trade = LedgerAction(
            type="trade",
            asset="BTC",
            amount=Decimal("0.1"),
            fee=Decimal("0.001"),
            timestamp="2025-04-17 10:00:00",
            refid="12345",
        )
        with self.assertRaises(MismatchedTradeError):
            self.lp.process_ledger([crypto_trade])  # EUR row missing

    def test_deposit_notfound(self):
        """Test handling deposit with no matching withdraw."""
        deposit = LedgerAction(
            type="deposit",
            asset="BTC",
            amount=Decimal("1.00"),
            fee=Decimal("0.00"),
            timestamp="2025-04-17 10:00:00",
            refid="67890",
        )

        with self.assertRaises(TradeNotFound):
            self.lp.process_ledger([deposit])

    def test_deposit_before_trade(self):
        """Test depositing before the trade occurred.

        Can't work anyway b/c there's no matching withdraw.
        """

        withdrawal = LedgerAction(
            type="withdrawal",
            asset="BTC",
            # already includes removed fee
            amount=Decimal("-0.098"),
            fee=Decimal("0.001"),
            timestamp="2025-04-18 12:00:00",
            refid="67890",
        )

        deposit = LedgerAction(
            type="deposit",
            asset="BTC",
            amount=Decimal("0.098"),
            fee=Decimal("0.001"),
            timestamp="2024-04-18 12:00:00",
            refid="ABCDE",
        )

        eur_trade = LedgerAction(
            type="trade",
            asset="EUR",
            amount=Decimal("-500.00"),
            fee=Decimal("2.00"),
            timestamp="2025-04-17 10:00:00",
            refid="12345",
        )
        crypto_trade = LedgerAction(
            type="trade",
            asset="BTC",
            amount=Decimal("0.1"),
            fee=Decimal("0.001"),
            timestamp="2025-04-17 10:00:00",
            refid="12345",
        )

        with self.assertRaises(TradeNotFound):
            self.lp.process_ledger([eur_trade, withdrawal, crypto_trade, deposit])

    def test_withdraw_before_trade(self):
        """Test withdrawing before the trade occurred."""

        withdrawal = LedgerAction(
            type="withdrawal",
            asset="BTC",
            # already includes removed fee
            amount=Decimal("-0.098"),
            fee=Decimal("0.001"),
            timestamp="2024-04-17 12:00:00",
            refid="67890",
        )

        eur_trade = LedgerAction(
            type="trade",
            asset="EUR",
            amount=Decimal("-500.00"),
            fee=Decimal("2.00"),
            timestamp="2025-04-17 10:00:00",
            refid="12345",
        )
        crypto_trade = LedgerAction(
            type="trade",
            asset="BTC",
            amount=Decimal("0.1"),
            fee=Decimal("0.001"),
            timestamp="2025-04-17 10:00:00",
            refid="12345",
        )

        with self.assertRaises(TradeNotFound):
            self.lp.process_ledger([eur_trade, withdrawal, crypto_trade])

    def test_withdraw_afterbalance(self):
        """Test withdrawing and subsequently checking balance."""

        eur_trade = LedgerAction(
            type="trade",
            asset="EUR",
            amount=Decimal("-500.00"),
            fee=Decimal("2.00"),
            timestamp="2025-04-17 10:00:00",
            refid="12345",
        )
        crypto_trade = LedgerAction(
            type="trade",
            asset="BTC",
            amount=Decimal("0.1"),
            fee=Decimal("0.001"),
            timestamp="2025-04-17 10:00:00",
            refid="12345",
        )

        withdrawal = LedgerAction(
            type="withdrawal",
            asset="BTC",
            # already includes removed fee
            amount=Decimal("-0.098"),
            fee=Decimal("0.001"),
            timestamp="2025-04-17 12:00:00",
            refid="67890",
        )

        self.lp.process_ledger([eur_trade, crypto_trade, withdrawal])

        self.assertEqual(len(self.lp.fifo_queues["BTC"]), 0)
        self.assertEqual(
            self.lp.fifo_queues["BTC"].get_remaining_amount(), Decimal("0.0")
        )
        self.assertEqual(len(self.lp.external_wallet["BTC"]), 1)
        self.assertEqual(
            self.lp.external_wallet["BTC"].get_remaining_amount(), Decimal("0.098")
        )

    def test_withdraw_and_deposit_afterbalance(self):
        """Test withdrawing, then depositing, and subsequently checking balance."""

        eur_trade = LedgerAction(
            type="trade",
            asset="EUR",
            amount=Decimal("-500.00"),
            fee=Decimal("2.00"),
            timestamp="2025-04-17 10:00:00",
            refid="12345",
        )
        crypto_trade = LedgerAction(
            type="trade",
            asset="BTC",
            amount=Decimal("0.1"),
            fee=Decimal("0.001"),
            timestamp="2025-04-17 10:00:00",
            refid="12345",
        )

        withdrawal = LedgerAction(
            type="withdrawal",
            asset="BTC",
            # already includes removed fee
            amount=Decimal("-0.098"),
            fee=Decimal("0.001"),
            timestamp="2025-04-17 12:00:00",
            refid="67890",
        )

        # deposit: lists full amount first, then subtracts fee
        deposit = LedgerAction(
            type="deposit",
            asset="BTC",
            amount=Decimal("0.098"),
            fee=Decimal("0.001"),
            timestamp="2025-04-17 14:00:00",
            refid="55555",
        )

        self.lp.process_ledger([eur_trade, crypto_trade, withdrawal, deposit])

        self.assertEqual(len(self.lp.fifo_queues["BTC"]), 1)
        self.assertEqual(
            self.lp.fifo_queues["BTC"].get_remaining_amount(), Decimal("0.097")
        )
        self.assertEqual(len(self.lp.external_wallet["BTC"]), 0)
        self.assertEqual(
            self.lp.external_wallet["BTC"].get_remaining_amount(), Decimal("0.0")
        )


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