"""
Inventory system for STARWASTE
Manages player inventory, equipment slots, and item stacks
"""

from typing import Optional, Dict, Any, List, Tuple
from dataclasses import dataclass, field
import sys
import os

# Add parent directory to path for imports
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from shared.items import Item, ItemType, EquipmentSlot, get_item


@dataclass
class ItemStack:
    """Represents a stack of items in a slot"""
    item_id: str
    quantity: int = 1
    durability: Optional[int] = None  # for tools/weapons/armor
    
    def __post_init__(self):
        """Initialize durability from item definition if needed"""
        if self.durability is None:
            item = get_item(self.item_id)
            if item and item.stats and item.stats.max_durability > 0:
                self.durability = item.stats.max_durability
    
    @property
    def item(self) -> Optional[Item]:
        """Get the item definition"""
        return get_item(self.item_id)
    
    @property
    def max_stack_size(self) -> int:
        """Get max stack size for this item"""
        item = self.item
        return item.stack_size if item else 1
    
    def can_stack_with(self, other: "ItemStack") -> bool:
        """Check if this stack can be merged with another"""
        if self.item_id != other.item_id:
            return False
        # Items with durability don't stack
        if self.durability is not None or other.durability is not None:
            return False
        return True
    
    def add_quantity(self, amount: int) -> int:
        """
        Add quantity to this stack.
        Returns amount that couldn't be added (overflow).
        """
        max_add = self.max_stack_size - self.quantity
        actual_add = min(amount, max_add)
        self.quantity += actual_add
        return amount - actual_add  # return overflow
    
    def remove_quantity(self, amount: int) -> int:
        """
        Remove quantity from this stack.
        Returns amount actually removed.
        """
        actual_remove = min(amount, self.quantity)
        self.quantity -= actual_remove
        return actual_remove
    
    def to_dict(self) -> Dict[str, Any]:
        """Serialize to dict for network transmission"""
        data = {
            "item_id": self.item_id,
            "quantity": self.quantity,
        }
        if self.durability is not None:
            data["durability"] = self.durability
        return data
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "ItemStack":
        """Deserialize from dict"""
        return cls(
            item_id=data["item_id"],
            quantity=data["quantity"],
            durability=data.get("durability"),
        )


class Inventory:
    """
    Player inventory with main slots, equipment slots, and hotbar.
    
    Layout:
    - Main inventory: 40 slots (8x5 grid)
    - Equipment: 8 slots (helmet, chest, legs, weapon, tool, accessory, jetpack, shield)
    - Hotbar: 10 slots (quick access, linked to main inventory slots 0-9)
    """
    
    def __init__(self, main_slots: int = 40):
        self.main_slots: int = main_slots
        self.items: Dict[int, ItemStack] = {}  # slot_index -> ItemStack
        
        # Equipment slots (separate from main inventory)
        self.equipment: Dict[EquipmentSlot, Optional[ItemStack]] = {
            EquipmentSlot.HELMET: None,
            EquipmentSlot.CHEST: None,
            EquipmentSlot.LEGS: None,
            EquipmentSlot.WEAPON: None,
            EquipmentSlot.TOOL: None,
            EquipmentSlot.ACCESSORY: None,
            EquipmentSlot.JETPACK: None,
            EquipmentSlot.SHIELD: None,
        }
        
        # Hotbar references first 10 slots of main inventory
        self.hotbar_size: int = 10
        self.selected_hotbar_slot: int = 0  # 0-9
    
    # ========================================================================
    # MAIN INVENTORY OPERATIONS
    # ========================================================================
    
    def get_item_stack(self, slot: int) -> Optional[ItemStack]:
        """Get item stack at slot"""
        return self.items.get(slot)
    
    def set_item_stack(self, slot: int, stack: Optional[ItemStack]) -> bool:
        """Set item stack at slot"""
        if slot < 0 or slot >= self.main_slots:
            return False
        
        if stack is None:
            if slot in self.items:
                del self.items[slot]
        else:
            self.items[slot] = stack
        
        return True
    
    def add_item(self, item_id: str, quantity: int = 1) -> int:
        """
        Add item to inventory (auto-stacking).
        Returns quantity that couldn't be added.
        """
        item = get_item(item_id)
        if not item:
            return quantity
        
        remaining = quantity
        
        # First, try to stack with existing stacks
        if item.stack_size > 1:
            for slot in range(self.main_slots):
                if remaining <= 0:
                    break
                
                stack = self.get_item_stack(slot)
                if stack and stack.item_id == item_id:
                    overflow = stack.add_quantity(remaining)
                    remaining = overflow
        
        # Then, create new stacks in empty slots
        while remaining > 0:
            empty_slot = self.find_empty_slot()
            if empty_slot is None:
                break  # Inventory full
            
            stack_size = min(remaining, item.stack_size)
            new_stack = ItemStack(item_id=item_id, quantity=stack_size)
            self.set_item_stack(empty_slot, new_stack)
            remaining -= stack_size
        
        return remaining  # Return what couldn't be added
    
    def remove_item(self, item_id: str, quantity: int = 1) -> int:
        """
        Remove item from inventory.
        Returns quantity actually removed.
        """
        removed = 0
        
        for slot in range(self.main_slots):
            if removed >= quantity:
                break
            
            stack = self.get_item_stack(slot)
            if stack and stack.item_id == item_id:
                to_remove = min(quantity - removed, stack.quantity)
                stack.remove_quantity(to_remove)
                removed += to_remove
                
                # Remove empty stacks
                if stack.quantity <= 0:
                    self.set_item_stack(slot, None)
        
        return removed
    
    def has_item(self, item_id: str, quantity: int = 1) -> bool:
        """Check if inventory has at least this quantity of item"""
        count = self.count_item(item_id)
        return count >= quantity
    
    def count_item(self, item_id: str) -> int:
        """Count total quantity of item in inventory"""
        total = 0
        for stack in self.items.values():
            if stack.item_id == item_id:
                total += stack.quantity
        return total
    
    def find_empty_slot(self) -> Optional[int]:
        """Find first empty slot in main inventory"""
        for slot in range(self.main_slots):
            if slot not in self.items:
                return slot
        return None
    
    def is_full(self) -> bool:
        """Check if inventory is completely full"""
        return len(self.items) >= self.main_slots
    
    def clear(self):
        """Clear all items from inventory"""
        self.items.clear()
        for slot in self.equipment:
            self.equipment[slot] = None
    
    # ========================================================================
    # EQUIPMENT OPERATIONS
    # ========================================================================
    
    def get_equipped(self, slot: EquipmentSlot) -> Optional[ItemStack]:
        """Get equipped item in slot"""
        return self.equipment.get(slot)
    
    def equip_item(self, slot: EquipmentSlot, stack: ItemStack) -> Optional[ItemStack]:
        """
        Equip item to equipment slot.
        Returns previously equipped item (if any).
        """
        # Verify item can be equipped in this slot
        if stack.item:
            if stack.item.equipment_slot != slot:
                return None  # Wrong slot type
        
        old_stack = self.equipment.get(slot)
        self.equipment[slot] = stack
        return old_stack
    
    def unequip_item(self, slot: EquipmentSlot) -> Optional[ItemStack]:
        """Unequip item from equipment slot"""
        stack = self.equipment.get(slot)
        if stack:
            self.equipment[slot] = None
        return stack
    
    def get_equipped_weapon(self) -> Optional[ItemStack]:
        """Get currently equipped weapon"""
        return self.get_equipped(EquipmentSlot.WEAPON)
    
    def get_equipped_tool(self) -> Optional[ItemStack]:
        """Get currently equipped tool"""
        return self.get_equipped(EquipmentSlot.TOOL)
    
    def get_total_armor(self) -> int:
        """Calculate total armor from all equipped armor pieces"""
        total_armor = 0
        for slot in [EquipmentSlot.HELMET, EquipmentSlot.CHEST, EquipmentSlot.LEGS]:
            stack = self.get_equipped(slot)
            if stack and stack.item and stack.item.stats:
                total_armor += stack.item.stats.armor
        return total_armor
    
    # ========================================================================
    # HOTBAR OPERATIONS
    # ========================================================================
    
    def get_hotbar_slot(self, hotbar_index: int) -> Optional[ItemStack]:
        """Get item in hotbar slot (0-9 maps to inventory slots 0-9)"""
        if 0 <= hotbar_index < self.hotbar_size:
            return self.get_item_stack(hotbar_index)
        return None
    
    def select_hotbar_slot(self, hotbar_index: int) -> bool:
        """Select hotbar slot (0-9)"""
        if 0 <= hotbar_index < self.hotbar_size:
            self.selected_hotbar_slot = hotbar_index
            return True
        return False
    
    def get_selected_hotbar_item(self) -> Optional[ItemStack]:
        """Get currently selected hotbar item"""
        return self.get_hotbar_slot(self.selected_hotbar_slot)
    
    # ========================================================================
    # SERIALIZATION (for network sync)
    # ========================================================================
    
    def to_dict(self) -> Dict[str, Any]:
        """Serialize inventory to dict for network transmission"""
        return {
            "main_slots": self.main_slots,
            "items": {
                str(slot): stack.to_dict()
                for slot, stack in self.items.items()
            },
            "equipment": {
                slot.value: stack.to_dict() if stack else None
                for slot, stack in self.equipment.items()
            },
            "selected_hotbar_slot": self.selected_hotbar_slot,
        }
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Inventory":
        """Deserialize inventory from dict"""
        inventory = cls(main_slots=data["main_slots"])
        
        # Restore main inventory items
        for slot_str, stack_data in data["items"].items():
            slot = int(slot_str)
            stack = ItemStack.from_dict(stack_data)
            inventory.set_item_stack(slot, stack)
        
        # Restore equipment
        for slot_str, stack_data in data["equipment"].items():
            slot = EquipmentSlot(slot_str)
            if stack_data:
                stack = ItemStack.from_dict(stack_data)
                inventory.equipment[slot] = stack
        
        inventory.selected_hotbar_slot = data["selected_hotbar_slot"]
        
        return inventory
    
    # ========================================================================
    # DEBUG / DISPLAY
    # ========================================================================
    
    def print_inventory(self):
        """Debug: Print inventory contents"""
        console.print("\n" + "="*60)
        console.print("INVENTORY")
        console.print("="*60)
        
        console.print("\n📦 MAIN INVENTORY:")
        for slot in range(self.main_slots):
            stack = self.get_item_stack(slot)
            if stack:
                item = stack.item
                name = item.name if item else stack.item_id
                durability_str = ""
                if stack.durability is not None:
                    durability_str = f" [{stack.durability}/{stack.item.stats.max_durability}]"
                console.print(f"  [{slot:2d}] {name:30} x{stack.quantity}{durability_str}")
        
        console.print("\n⚔️ EQUIPMENT:")
        for slot, stack in self.equipment.items():
            if stack:
                item = stack.item
                name = item.name if item else stack.item_id
                durability_str = ""
                if stack.durability is not None:
                    durability_str = f" [{stack.durability}/{stack.item.stats.max_durability}]"
                console.print(f"  {slot.value:12s}: {name}{durability_str}")
        
        console.print(f"\n🎯 HOTBAR: Slot {self.selected_hotbar_slot} selected")
        console.print(f"   Total Armor: {self.get_total_armor()}")


# ============================================================================
# TESTING
# ============================================================================

if __name__ == "__main__":
    console.print("Testing Inventory System...")
    
    # Create inventory
    inv = Inventory()
    
    # Add some items
    console.print("\n1. Adding items:")
    inv.add_item(ItemType.RESOURCE_SCRAP.value, 150)  # Should create 2 stacks (99 + 51)
    inv.add_item(ItemType.RESOURCE_WOOD.value, 30)
    inv.add_item(ItemType.RESOURCE_STONE.value, 45)
    inv.add_item(ItemType.WEAPON_MELEE_SCRAP_SWORD.value, 1)
    inv.add_item(ItemType.TOOL_PICKAXE.value, 1)
    inv.add_item(ItemType.ARMOR_HELMET_SCRAP.value, 1)
    inv.add_item(ItemType.ARMOR_CHEST_SCRAP.value, 1)
    
    inv.print_inventory()
    
    # Test equipment
    console.print("\n2. Equipping items:")
    # Find and equip sword
    for slot in range(inv.main_slots):
        stack = inv.get_item_stack(slot)
        if stack and stack.item_id == ItemType.WEAPON_MELEE_SCRAP_SWORD.value:
            inv.set_item_stack(slot, None)  # Remove from inventory
            inv.equip_item(EquipmentSlot.WEAPON, stack)
            break
    
    # Find and equip helmet
    for slot in range(inv.main_slots):
        stack = inv.get_item_stack(slot)
        if stack and stack.item_id == ItemType.ARMOR_HELMET_SCRAP.value:
            inv.set_item_stack(slot, None)
            inv.equip_item(EquipmentSlot.HELMET, stack)
            break
    
    # Find and equip chest
    for slot in range(inv.main_slots):
        stack = inv.get_item_stack(slot)
        if stack and stack.item_id == ItemType.ARMOR_CHEST_SCRAP.value:
            inv.set_item_stack(slot, None)
            inv.equip_item(EquipmentSlot.CHEST, stack)
            break
    
    inv.print_inventory()
    
    # Test serialization
    console.print("\n3. Testing serialization:")
    serialized = inv.to_dict()
    console.print(f"Serialized keys: {list(serialized.keys())}")
    
    deserialized = Inventory.from_dict(serialized)
    console.print("\nDeserialized inventory:")
    deserialized.print_inventory()
    
    # Test item counting
    console.print("\n4. Item counting:")
    console.print(f"Scrap Metal count: {inv.count_item(ItemType.RESOURCE_SCRAP.value)}")
    console.print(f"Has 100 Scrap? {inv.has_item(ItemType.RESOURCE_SCRAP.value, 100)}")
    console.print(f"Has 200 Scrap? {inv.has_item(ItemType.RESOURCE_SCRAP.value, 200)}")
