"""
Unit tests for FreeExile 2D Spatial Grid Slot System for Inventory and Stash.
Follows Autonomous TDD Protocol: Formulating tests 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.spatial_grid_types import ItemDimensions, GridPlacement, get_default_item_dimensions
from inventory.spatial_inventory_grid import SpatialInventoryGrid
from inventory.inventory_types import ItemType, InventoryItem
from world.primal_stones_crafting import ItemRarity


class TestSpatialInventoryGrid(unittest.TestCase):
    def setUp(self):
        # 12 Columns x 5 Rows (Standard PoE ARPG Inventory = 60 cells)
        self.bag_grid = SpatialInventoryGrid(width=12, height=5)
        # 12 Columns x 12 Rows (Standard PoE Stash Tab = 144 cells)
        self.stash_grid = SpatialInventoryGrid(width=12, height=12)

    def test_default_equipment_dimensions(self):
        """Equipment archetypes must return correct W x H spatial bounding boxes."""
        # 2H Greatsword / Greataxe / Bow / Halberd: 2 x 4 or 2 x 3
        dim_greatsword = get_default_item_dimensions("wpn_abyssal_chainguard_sword", ItemType.WEAPON)
        self.assertEqual(dim_greatsword, ItemDimensions(width=2, height=4))

        # Body Armor: 2 x 3
        dim_armor = get_default_item_dimensions("armor_da_xoa_huyet_giap", ItemType.ARMOR)
        self.assertEqual(dim_armor, ItemDimensions(width=2, height=3))

        # Helmet / Boots / Gloves: 2 x 2
        dim_helm = get_default_item_dimensions("helm_chchien_tuong", ItemType.ARMOR, subtype="HELMET")
        self.assertEqual(dim_helm, ItemDimensions(width=2, height=2))

        # Rings / Amulets / Primal Bloodstones: 1 x 1
        dim_gem = get_default_item_dimensions("curr_blood_soul_gem", ItemType.CURRENCY)
        self.assertEqual(dim_gem, ItemDimensions(width=1, height=1))

    def test_place_multicell_item_within_bounds(self):
        """A 2x4 greatsword placed at (0, 0) occupies exactly 8 cells: (0..1, 0..3)."""
        sword = InventoryItem(
            item_uuid="sword_01",
            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,
        )
        # Place at x=0, y=0
        placed = self.bag_grid.place_item(sword, x=0, y=0, width=2, height=4)
        self.assertTrue(placed)

        # Check occupancy of all 8 cells
        for cx in range(0, 2):
            for cy in range(0, 4):
                self.assertTrue(self.bag_grid.is_cell_occupied(cx, cy))
                self.assertEqual(self.bag_grid.get_item_at(cx, cy), sword)

        # Cell (2, 0) and (0, 4) should remain free
        self.assertFalse(self.bag_grid.is_cell_occupied(2, 0))
        self.assertFalse(self.bag_grid.is_cell_occupied(0, 4))

    def test_reject_out_of_bounds_placement(self):
        """Placing a 2x4 item where x + width > 12 or y + height > 5 must be rejected."""
        sword = InventoryItem(
            item_uuid="sword_oob",
            item_id="wpn_abyssal_chainguard_sword",
            name="Cự Kiếm",
            item_type=ItemType.WEAPON,
        )
        # x=11, w=2 -> x+w=13 > 12 (exceeds width)
        self.assertFalse(self.bag_grid.can_place(x=11, y=0, width=2, height=4))
        self.assertFalse(self.bag_grid.place_item(sword, x=11, y=0, width=2, height=4))

        # y=2, h=4 -> y+h=6 > 5 (exceeds height)
        self.assertFalse(self.bag_grid.can_place(x=0, y=2, width=2, height=4))
        self.assertFalse(self.bag_grid.place_item(sword, x=0, y=2, width=2, height=4))

    def test_reject_overlapping_collision(self):
        """Two items cannot share overlapping cells."""
        armor = InventoryItem(
            item_uuid="armor_01",
            item_id="armor_da_xoa_huyet_giap",
            name="Huyết Giáp",
            item_type=ItemType.ARMOR,
        )
        # Place armor (2x3) at (2, 1) -> occupies x in [2, 3], y in [1, 3]
        self.assertTrue(self.bag_grid.place_item(armor, x=2, y=1, width=2, height=3))

        helm = InventoryItem(
            item_uuid="helm_01",
            item_id="helm_01",
            name="Mũ",
            item_type=ItemType.ARMOR,
        )
        # Attempt to place helm (2x2) at (3, 2) -> overlaps with (3, 2) and (3, 3)
        self.assertFalse(self.bag_grid.can_place(x=3, y=2, width=2, height=2))
        self.assertFalse(self.bag_grid.place_item(helm, x=3, y=2, width=2, height=2))

        # Placing at (4, 1) has no overlap and must succeed
        self.assertTrue(self.bag_grid.can_place(x=4, y=1, width=2, height=2))
        self.assertTrue(self.bag_grid.place_item(helm, x=4, y=1, width=2, height=2))

    def test_first_fit_auto_placement(self):
        """First-fit scan automatically finds the first available (x, y) coordinates."""
        # Bag is empty, first fit for 2x4 should be (0, 0)
        pos = self.bag_grid.find_first_fit(width=2, height=4)
        self.assertEqual(pos, (0, 0))

        # Place sword at (0, 0)
        sword = InventoryItem(item_uuid="sw1", item_id="sw", name="Kiếm", item_type=ItemType.WEAPON)
        self.bag_grid.place_item(sword, x=0, y=0, width=2, height=4)

        # Next 2x4 item should find (2, 0)
        next_pos = self.bag_grid.find_first_fit(width=2, height=4)
        self.assertEqual(next_pos, (2, 0))

    def test_remove_item_frees_all_occupied_cells(self):
        """Removing an item frees all of its occupied grid coordinates."""
        sword = InventoryItem(item_uuid="sw_rem", item_id="sw", name="Kiếm", item_type=ItemType.WEAPON)
        self.bag_grid.place_item(sword, x=4, y=1, width=2, height=4)
        self.assertTrue(self.bag_grid.is_cell_occupied(5, 4))

        removed = self.bag_grid.remove_item("sw_rem")
        self.assertEqual(removed, sword)
        self.assertFalse(self.bag_grid.is_cell_occupied(4, 1))
        self.assertFalse(self.bag_grid.is_cell_occupied(5, 4))

    def test_auto_compact_and_defragment(self):
        """Auto-compact defragments the inventory by packing largest items first."""
        # Place fragmented small and large items
        ring = InventoryItem(item_uuid="r1", item_id="r1", name="Nhẫn", item_type=ItemType.RELIC)
        sword = InventoryItem(item_uuid="s1", item_id="s1", name="Cự Kiếm", item_type=ItemType.WEAPON)
        armor = InventoryItem(item_uuid="a1", item_id="a1", name="Giáp", item_type=ItemType.ARMOR)

        # Place ring at (0, 0) (blocking a big item)
        self.bag_grid.place_item(ring, x=0, y=0, width=1, height=1)
        # Place armor at (4, 2)
        self.bag_grid.place_item(armor, x=4, y=2, width=2, height=3)
        # Place sword at (8, 0)
        self.bag_grid.place_item(sword, x=8, y=0, width=2, height=4)

        # Run auto-compact
        success = self.bag_grid.auto_compact()
        self.assertTrue(success)

        # The largest item (sword 2x4) should now be packed at top-left (0, 0)
        sword_placement = self.bag_grid.get_item_placement("s1")
        self.assertIsNotNone(sword_placement)
        self.assertEqual((sword_placement.x, sword_placement.y), (0, 0))


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