"""
Unit tests for FreeExile Character Inventory, Personal Stash, and Premium Merchant Stash.
Follows Autonomous TDD Protocol: Tests formulation before implementation.
"""

import unittest
import sys
import os

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

from inventory.inventory_types import (
    ItemType,
    EquipmentSlot,
    InventoryItem,
    CharacterInventory,
    PersonalStash,
    MerchantStashTab,
)
from inventory.inventory_service import InventoryService
from shop.shop_service import MegaShopService
from world.primal_stones_crafting import ItemRarity


class TestInventoryStashSystem(unittest.TestCase):
    def setUp(self):
        self.shop_service = MegaShopService()
        self.inv_service = InventoryService(shop_service=self.shop_service)
        self.account_id = "exile_warrior_999"
        self.character_id = "char_berserker_01"
        self.shop_service.register_player(self.account_id)
        self.inv_service.get_or_create_character_storage(self.account_id, self.character_id)

    def test_inventory_add_and_move_item(self):
        """Player can add items to bag and rearrange item slots."""
        sword = InventoryItem(
            item_uuid="item_sword_001",
            item_id="wpn_abyssal_chainguard_sword",
            name="Xích Huyết Trảm Ma Cự Kiếm",
            item_type=ItemType.WEAPON,
            rarity=ItemRarity.CUC_PHAM,
            item_level=85,
        )
        success, msg, slot = self.inv_service.add_item_to_inventory(
            self.account_id, self.character_id, sword, preferred_slot=0
        )
        self.assertTrue(success)
        self.assertEqual(slot, 0)

        # Move item from slot 0 to slot 5
        move_success, move_msg = self.inv_service.move_inventory_item(
            self.account_id, self.character_id, from_slot=0, to_slot=5
        )
        self.assertTrue(move_success)

        inv = self.inv_service.get_character_inventory(self.account_id, self.character_id)
        self.assertNotIn(0, inv.slots)
        self.assertIn(5, inv.slots)
        self.assertEqual(inv.slots[5].item_uuid, "item_sword_001")

    def test_equip_and_unequip_gear(self):
        """Player can equip weapons from inventory and unequip back to bag."""
        sword = InventoryItem(
            item_uuid="item_sword_002",
            item_id="wpn_abyssal_chainguard_sword",
            name="Xích Huyết Trảm Ma Cự Kiếm",
            item_type=ItemType.WEAPON,
            rarity=ItemRarity.CUC_PHAM,
            item_level=85,
        )
        self.inv_service.add_item_to_inventory(
            self.account_id, self.character_id, sword, preferred_slot=2
        )

        # Equip to MAIN_HAND
        eq_success, eq_msg = self.inv_service.equip_item(
            self.account_id, self.character_id, inv_slot=2, equip_slot=EquipmentSlot.MAIN_HAND
        )
        self.assertTrue(eq_success)

        inv = self.inv_service.get_character_inventory(self.account_id, self.character_id)
        self.assertNotIn(2, inv.slots)
        self.assertIn(EquipmentSlot.MAIN_HAND, inv.equipment)
        self.assertEqual(inv.equipment[EquipmentSlot.MAIN_HAND].item_uuid, "item_sword_002")

        # Unequip back to bag
        uneq_success, uneq_msg = self.inv_service.unequip_item(
            self.account_id, self.character_id, equip_slot=EquipmentSlot.MAIN_HAND, target_inv_slot=3
        )
        self.assertTrue(uneq_success)
        self.assertNotIn(EquipmentSlot.MAIN_HAND, inv.equipment)
        self.assertIn(3, inv.slots)

    def test_deposit_and_withdraw_personal_stash(self):
        """Player can deposit items from bag to stash and withdraw back."""
        gem = InventoryItem(
            item_uuid="item_gem_001",
            item_id="curr_blood_soul_gem",
            name="Huyết Hồn Thạch",
            item_type=ItemType.CURRENCY,
            rarity=ItemRarity.LINH_PHAM,
            item_level=70,
            stack_count=50,
            max_stack=5000,
        )
        self.inv_service.add_item_to_inventory(
            self.account_id, self.character_id, gem, preferred_slot=1
        )

        # Deposit to general stash tab
        dep_success, dep_msg = self.inv_service.deposit_to_stash(
            self.account_id, self.character_id, inv_slot=1, tab_id="tab_general", stash_slot=0
        )
        self.assertTrue(dep_success)

        stash = self.inv_service.get_personal_stash(self.account_id, self.character_id)
        self.assertIn(0, stash.tabs["tab_general"].slots)
        self.assertEqual(stash.tabs["tab_general"].slots[0].item_uuid, "item_gem_001")

        # Withdraw back to inventory
        wd_success, wd_msg = self.inv_service.withdraw_from_stash(
            self.account_id, self.character_id, tab_id="tab_general", stash_slot=0, target_inv_slot=7
        )
        self.assertTrue(wd_success)
        self.assertNotIn(0, stash.tabs["tab_general"].slots)

        inv = self.inv_service.get_character_inventory(self.account_id, self.character_id)
        self.assertIn(7, inv.slots)
        self.assertEqual(inv.slots[7].item_uuid, "item_gem_001")

    def test_merchant_stash_tab_is_locked_by_default(self):
        """Merchant Stash tab is a premium feature and is LOCKED by default."""
        is_unlocked = self.inv_service.is_merchant_stash_unlocked(self.account_id, self.character_id)
        self.assertFalse(is_unlocked)

        # Attempting to list item in locked merchant stash fails with clear error
        sword = InventoryItem(
            item_uuid="item_sword_003",
            item_id="wpn_dragonbone_maw_greataxe",
            name="Thôn Cốt Long Ngạc Cự Phủ",
            item_type=ItemType.WEAPON,
            rarity=ItemRarity.THAN_THOAI,
            item_level=90,
        )
        self.inv_service.add_item_to_inventory(
            self.account_id, self.character_id, sword, preferred_slot=0
        )

        list_success, list_msg = self.inv_service.place_item_in_merchant_stash(
            account_id=self.account_id,
            character_id=self.character_id,
            inv_slot=0,
            merchant_slot=0,
            price_currency="STONE_HON_NGUYEN",
            price_amount=25,
        )
        self.assertFalse(list_success)
        self.assertIn("locked", list_msg.lower())
        self.assertIn("megashop", list_msg.lower())

    def test_unlock_merchant_stash_via_megashop_coins(self):
        """Purchasing the Premium Merchant Stash Tab in MegaShop with coins unlocks the tab."""
        # 1. Player has insufficient coins initially
        self.assertEqual(self.shop_service.get_player_coin_balance(self.account_id), 0)
        unlock_success, unlock_msg = self.inv_service.unlock_merchant_stash_with_coins(
            self.account_id, self.character_id
        )
        self.assertFalse(unlock_success)
        self.assertIn("insufficient", unlock_msg.lower())

        # 2. Credit player with 500 Gold Cores (Huyết Cổ Tệ)
        self.shop_service.credit_player_coins(self.account_id, 500, reason="test_deposit")
        self.assertEqual(self.shop_service.get_player_coin_balance(self.account_id), 500)

        # 3. Purchase & Unlock
        unlock_success, unlock_msg = self.inv_service.unlock_merchant_stash_with_coins(
            self.account_id, self.character_id
        )
        self.assertTrue(unlock_success)
        self.assertTrue(self.inv_service.is_merchant_stash_unlocked(self.account_id, self.character_id))
        self.assertEqual(self.shop_service.get_player_coin_balance(self.account_id), 0)

        # 4. Now listing items in Merchant Stash succeeds and sets asking price
        sword = InventoryItem(
            item_uuid="item_sword_004",
            item_id="wpn_dragonbone_maw_greataxe",
            name="Thôn Cốt Long Ngạc Cự Phủ",
            item_type=ItemType.WEAPON,
            rarity=ItemRarity.THAN_THOAI,
            item_level=90,
        )
        self.inv_service.add_item_to_inventory(
            self.account_id, self.character_id, sword, preferred_slot=0
        )

        list_success, list_msg = self.inv_service.place_item_in_merchant_stash(
            account_id=self.account_id,
            character_id=self.character_id,
            inv_slot=0,
            merchant_slot=0,
            price_currency="STONE_HON_NGUYEN",
            price_amount=25,
        )
        self.assertTrue(list_success)

        merchant_tab = self.inv_service.get_merchant_stash(self.account_id, self.character_id)
        self.assertIn(0, merchant_tab.slots)
        item_in_slot = merchant_tab.slots[0]
        self.assertEqual(item_in_slot.item_uuid, "item_sword_004")
        self.assertEqual(item_in_slot.asking_price_currency, "STONE_HON_NGUYEN")
        self.assertEqual(item_in_slot.asking_price_amount, 25)

    def test_merchant_stash_price_update_and_withdrawal(self):
        """Seller can update listing price and withdraw unsold items back to inventory."""
        # Grant coins and unlock
        self.shop_service.credit_player_coins(self.account_id, 1000)
        self.inv_service.unlock_merchant_stash_with_coins(self.account_id, self.character_id)

        axe = InventoryItem(
            item_uuid="item_axe_005",
            item_id="wpn_dragonbone_maw_greataxe",
            name="Thôn Cốt Long Ngạc Cự Phủ",
            item_type=ItemType.WEAPON,
            rarity=ItemRarity.CUC_PHAM,
            item_level=80,
        )
        self.inv_service.add_item_to_inventory(self.account_id, self.character_id, axe, preferred_slot=1)
        self.inv_service.place_item_in_merchant_stash(
            self.account_id, self.character_id, inv_slot=1, merchant_slot=2,
            price_currency="STONE_HON_NGUYEN", price_amount=10
        )

        # Update price to 15 Hỗn Nguyên Thạch
        upd_success, upd_msg = self.inv_service.update_merchant_item_price(
            self.account_id, self.character_id, merchant_slot=2,
            new_currency="STONE_HON_NGUYEN", new_amount=15
        )
        self.assertTrue(upd_success)
        m_tab = self.inv_service.get_merchant_stash(self.account_id, self.character_id)
        self.assertEqual(m_tab.slots[2].asking_price_amount, 15)

        # Withdraw item back to inventory
        wd_success, wd_msg = self.inv_service.remove_item_from_merchant_stash(
            self.account_id, self.character_id, merchant_slot=2, target_inv_slot=3
        )
        self.assertTrue(wd_success)
        self.assertNotIn(2, m_tab.slots)

        inv = self.inv_service.get_character_inventory(self.account_id, self.character_id)
        self.assertIn(3, inv.slots)
        self.assertEqual(inv.slots[3].item_uuid, "item_axe_005")


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