"""
Unit tests for Two-Phase Commit (2PC) Instant Buyout Trade Engine in FreeExile.
Verifies zero-duplication atomic swaps, rollback on insufficient funds, and concurrent trade lock rejection
using FreeExile original currencies (ThienMenh / Destiny Stone, HonNguyen / Primal Chaos Stone).
"""

import unittest
import sys
import os

# Add server directory to path
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../server")))

from trade.two_phase_commit import InstantBuyoutEngine, StashItem


class TestInstantBuyout2PC(unittest.TestCase):
    def setUp(self):
        self.engine = InstantBuyoutEngine(db_path=":memory:")
        self.seller = self.engine.register_account("seller_account_01")
        self.buyer = self.engine.register_account("buyer_account_02")

        # Seller lists a rare sword for 5 ThienMenh stones
        self.item_uuid = "item_sword_godtier_999"
        sword = StashItem(
            item_uuid=self.item_uuid,
            owner_account_id="seller_account_01",
            item_name="Lôi Đình Tuyệt Ảnh Kiếm",
            asking_price_currency="ThienMenh",
            asking_price_amount=5
        )
        self.seller.items[self.item_uuid] = sword

    def test_successful_atomic_swap(self):
        """Buyer has 10 ThienMenh Stones, purchases 5 ThienMenh sword -> atomic swap succeeds."""
        self.buyer.currencies["ThienMenh"] = 10
        self.seller.currencies["ThienMenh"] = 0

        success, msg, tx_id = self.engine.execute_instant_buyout(
            buyer_account_id="buyer_account_02",
            seller_account_id="seller_account_01",
            item_uuid=self.item_uuid,
            offered_currency="ThienMenh",
            offered_amount=5
        )

        self.assertTrue(success)
        self.assertIsNotNone(tx_id)
        # Verify balances
        self.assertEqual(self.buyer.currencies["ThienMenh"], 5)
        self.assertEqual(self.seller.currencies["ThienMenh"], 5)
        # Verify item transfer
        self.assertNotIn(self.item_uuid, self.seller.items)
        self.assertIn(self.item_uuid, self.buyer.items)
        self.assertEqual(self.buyer.items[self.item_uuid].owner_account_id, "buyer_account_02")
        # Verify ledger audit trail
        self.assertEqual(len(self.engine.transaction_log), 1)
        self.assertEqual(self.engine.transaction_log[0]["tx_id"], tx_id)

    def test_insufficient_funds_rollback(self):
        """Buyer has only 3 ThienMenh Stones (needs 5) -> transaction rejected, zero items moved."""
        self.buyer.currencies["ThienMenh"] = 3
        self.seller.currencies["ThienMenh"] = 0

        success, msg, tx_id = self.engine.execute_instant_buyout(
            buyer_account_id="buyer_account_02",
            seller_account_id="seller_account_01",
            item_uuid=self.item_uuid,
            offered_currency="ThienMenh",
            offered_amount=5
        )

        self.assertFalse(success)
        self.assertIn("insufficient funds", msg.lower())
        # Verify balances untouched
        self.assertEqual(self.buyer.currencies["ThienMenh"], 3)
        self.assertEqual(self.seller.currencies["ThienMenh"], 0)
        # Verify item stays with seller
        self.assertIn(self.item_uuid, self.seller.items)
        self.assertNotIn(self.item_uuid, self.buyer.items)
        self.assertFalse(self.seller.items[self.item_uuid].is_locked)

    def test_concurrent_lock_prevents_double_spend(self):
        """If item is currently locked, simultaneous buy attempt must be rejected."""
        self.buyer.currencies["ThienMenh"] = 10
        item = self.seller.items[self.item_uuid]
        item.is_locked = True # Simulate ongoing transaction

        success, msg, tx_id = self.engine.execute_instant_buyout(
            buyer_account_id="buyer_account_02",
            seller_account_id="seller_account_01",
            item_uuid=self.item_uuid,
            offered_currency="ThienMenh",
            offered_amount=5
        )

        self.assertFalse(success)
        self.assertIn("locked in another pending trade", msg.lower())

    def test_anti_inflationary_market_burning_tax(self):
        """When market_tax_rate is set to 5%, 5% of currency is permanently destroyed upon buyout."""
        tax_engine = InstantBuyoutEngine(market_tax_rate=0.05, db_path=":memory:")
        s_acc = tax_engine.register_account("seller_tax")
        b_acc = tax_engine.register_account("buyer_tax")

        item = StashItem(
            item_uuid="axe_tax_1",
            owner_account_id="seller_tax",
            item_name="Cuồng Lôi Rìu",
            asking_price_currency="HonNguyen",
            asking_price_amount=100
        )
        s_acc.items["axe_tax_1"] = item
        b_acc.currencies["HonNguyen"] = 150

        ok, msg, tx = tax_engine.execute_instant_buyout(
            buyer_account_id="buyer_tax",
            seller_account_id="seller_tax",
            item_uuid="axe_tax_1",
            offered_currency="HonNguyen",
            offered_amount=100
        )
        self.assertTrue(ok)
        # Buyer paid 100
        self.assertEqual(b_acc.currencies["HonNguyen"], 50)
        # Seller received 100 - 5 = 95
        self.assertEqual(s_acc.currencies["HonNguyen"], 95)
        self.assertEqual(tax_engine.total_tax_burned, 5)

    def test_scourge_soulbound_item_cannot_be_traded(self):
        """If an item is scourge soulbound, it cannot be traded on the black market."""
        self.buyer.currencies["ThienMenh"] = 10
        item = self.seller.items[self.item_uuid]
        item.is_scourge_soulbound = True

        success, msg, tx_id = self.engine.execute_instant_buyout(
            buyer_account_id="buyer_account_02",
            seller_account_id="seller_account_01",
            item_uuid=self.item_uuid,
            offered_currency="ThienMenh",
            offered_amount=5
        )

        self.assertFalse(success)
        self.assertIn("scourge soulbound", msg.lower())
        # Verify item stays with seller
        self.assertIn(self.item_uuid, self.seller.items)
        self.assertNotIn(self.item_uuid, self.buyer.items)


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