package items

import (
	"fmt"
	"testing"
)

func TestNewItem(t *testing.T) {
	item := NewItem()
	if item == nil {
		t.Fatal("NewItem returned nil")
	}

	if item.Details.UniqueID <= 0 {
		t.Error("New item should have a valid unique ID")
	}

	if item.Details.Count != 1 {
		t.Errorf("Expected count 1, got %d", item.Details.Count)
	}

	if item.GenericInfo.Condition != DefaultItemCondition {
		t.Errorf("Expected condition %d, got %d", DefaultItemCondition, item.GenericInfo.Condition)
	}
}

func TestNewItemFromTemplate(t *testing.T) {
	// Create template
	template := NewItem()
	template.Name = "Test Sword"
	template.Description = "A test weapon"
	template.Details.ItemID = 12345
	template.Details.Icon = 100
	template.GenericInfo.ItemType = ItemTypeWeapon
	template.WeaponInfo = &WeaponInfo{
		WieldType:   ItemWieldTypeSingle,
		DamageLow1:  10,
		DamageHigh1: 20,
		Delay:       30,
		Rating:      1.5,
	}

	// Create from template
	item := NewItemFromTemplate(template)
	if item == nil {
		t.Fatal("NewItemFromTemplate returned nil")
	}

	if item.Name != template.Name {
		t.Errorf("Expected name %s, got %s", template.Name, item.Name)
	}

	if item.Details.ItemID != template.Details.ItemID {
		t.Errorf("Expected item ID %d, got %d", template.Details.ItemID, item.Details.ItemID)
	}

	if item.Details.UniqueID == template.Details.UniqueID {
		t.Error("New item should have different unique ID from template")
	}

	if item.WeaponInfo == nil {
		t.Fatal("Weapon info should be copied")
	}

	if item.WeaponInfo.DamageLow1 != template.WeaponInfo.DamageLow1 {
		t.Errorf("Expected damage %d, got %d", template.WeaponInfo.DamageLow1, item.WeaponInfo.DamageLow1)
	}
}

func TestItemCopy(t *testing.T) {
	original := NewItem()
	original.Name = "Original Item"
	original.Details.ItemID = 999
	original.AddStat(&ItemStat{
		StatName: "Strength",
		StatType: ItemStatStr,
		Value:    10,
	})

	copy := original.Copy()
	if copy == nil {
		t.Fatal("Copy returned nil")
	}

	if copy.Name != original.Name {
		t.Errorf("Expected name %s, got %s", original.Name, copy.Name)
	}

	if copy.Details.UniqueID == original.Details.UniqueID {
		t.Error("Copy should have different unique ID")
	}

	if len(copy.ItemStats) != len(original.ItemStats) {
		t.Errorf("Expected %d stats, got %d", len(original.ItemStats), len(copy.ItemStats))
	}

	// Test nil copy
	var nilItem *Item
	nilCopy := nilItem.Copy()
	if nilCopy != nil {
		t.Error("Copy of nil should return nil")
	}
}

func TestItemValidation(t *testing.T) {
	// Valid item
	item := NewItem()
	item.Name = "Valid Item"
	item.Details.ItemID = 100

	result := item.Validate()
	if !result.Valid {
		t.Errorf("Valid item should pass validation: %v", result.Errors)
	}

	// Invalid item - no name
	invalidItem := NewItem()
	invalidItem.Details.ItemID = 100

	result = invalidItem.Validate()
	if result.Valid {
		t.Error("Item without name should fail validation")
	}

	// Invalid item - negative count
	invalidItem2 := NewItem()
	invalidItem2.Name = "Invalid Item"
	invalidItem2.Details.ItemID = 100
	invalidItem2.Details.Count = -1

	result = invalidItem2.Validate()
	if result.Valid {
		t.Error("Item with negative count should fail validation")
	}
}

func TestItemStats(t *testing.T) {
	item := NewItem()

	// Add a stat
	stat := &ItemStat{
		StatName: "Strength",
		StatType: ItemStatStr,
		Value:    15,
		Level:    1,
	}
	item.AddStat(stat)

	if len(item.ItemStats) != 1 {
		t.Errorf("Expected 1 stat, got %d", len(item.ItemStats))
	}

	// Check if item has stat
	if !item.HasStat(0, "Strength") {
		t.Error("Item should have Strength stat")
	}

	if !item.HasStat(uint32(ItemStatStr), "") {
		t.Error("Item should have STR stat by ID")
	}

	if item.HasStat(0, "Nonexistent") {
		t.Error("Item should not have nonexistent stat")
	}

	// Add stat by values
	item.AddStatByValues(ItemStatAgi, 0, 10, 1, "Agility")

	if len(item.ItemStats) != 2 {
		t.Errorf("Expected 2 stats, got %d", len(item.ItemStats))
	}
}

func TestItemFlags(t *testing.T) {
	item := NewItem()

	// Set flags
	item.GenericInfo.ItemFlags = Attuned | NoTrade
	item.GenericInfo.ItemFlags2 = Heirloom | Ornate

	// Test flag checking
	if !item.CheckFlag(Attuned) {
		t.Error("Item should be attuned")
	}

	if !item.CheckFlag(NoTrade) {
		t.Error("Item should be no-trade")
	}

	if item.CheckFlag(Lore) {
		t.Error("Item should not be lore")
	}

	if !item.CheckFlag2(Heirloom) {
		t.Error("Item should be heirloom")
	}

	if !item.CheckFlag2(Ornate) {
		t.Error("Item should be ornate")
	}

	if item.CheckFlag2(Refined) {
		t.Error("Item should not be refined")
	}
}

func TestItemLocking(t *testing.T) {
	item := NewItem()

	// Item should not be locked initially
	if item.IsItemLocked() {
		t.Error("New item should not be locked")
	}

	// Lock for crafting
	if !item.TryLockItem(LockReasonCrafting) {
		t.Error("Should be able to lock item for crafting")
	}

	if !item.IsItemLocked() {
		t.Error("Item should be locked")
	}

	if !item.IsItemLockedFor(LockReasonCrafting) {
		t.Error("Item should be locked for crafting")
	}

	if item.IsItemLockedFor(LockReasonHouse) {
		t.Error("Item should not be locked for house")
	}

	// Try to lock for another reason while already locked
	if item.TryLockItem(LockReasonHouse) {
		t.Error("Should not be able to lock for different reason")
	}

	// Unlock
	if !item.TryUnlockItem(LockReasonCrafting) {
		t.Error("Should be able to unlock item")
	}

	if item.IsItemLocked() {
		t.Error("Item should not be locked after unlock")
	}
}

func TestItemTypes(t *testing.T) {
	item := NewItem()

	// Test weapon
	item.GenericInfo.ItemType = ItemTypeWeapon
	if !item.IsWeapon() {
		t.Error("Item should be a weapon")
	}
	if item.IsArmor() {
		t.Error("Item should not be armor")
	}

	// Test armor
	item.GenericInfo.ItemType = ItemTypeArmor
	if !item.IsArmor() {
		t.Error("Item should be armor")
	}
	if item.IsWeapon() {
		t.Error("Item should not be a weapon")
	}

	// Test bag
	item.GenericInfo.ItemType = ItemTypeBag
	if !item.IsBag() {
		t.Error("Item should be a bag")
	}

	// Test food
	item.GenericInfo.ItemType = ItemTypeFood
	item.FoodInfo = &FoodInfo{Type: 1} // Food
	if !item.IsFood() {
		t.Error("Item should be food")
	}
	if !item.IsFoodFood() {
		t.Error("Item should be food (not drink)")
	}
	if item.IsFoodDrink() {
		t.Error("Item should not be drink")
	}

	// Test drink
	item.FoodInfo.Type = 0 // Drink
	if !item.IsFoodDrink() {
		t.Error("Item should be drink")
	}
	if item.IsFoodFood() {
		t.Error("Item should not be food")
	}
}

func TestMasterItemList(t *testing.T) {
	masterList := NewMasterItemList()
	if masterList == nil {
		t.Fatal("NewMasterItemList returned nil")
	}

	// Initial state
	if masterList.GetItemCount() != 0 {
		t.Error("New master list should be empty")
	}

	// Add item
	item := NewItem()
	item.Name = "Test Item"
	item.Details.ItemID = 12345

	masterList.AddItem(item)

	if masterList.GetItemCount() != 1 {
		t.Errorf("Expected 1 item, got %d", masterList.GetItemCount())
	}

	// Get item
	retrieved := masterList.GetItem(12345)
	if retrieved == nil {
		t.Fatal("GetItem returned nil")
	}

	if retrieved.Name != item.Name {
		t.Errorf("Expected name %s, got %s", item.Name, retrieved.Name)
	}

	// Get by name
	byName := masterList.GetItemByName("Test Item")
	if byName == nil {
		t.Fatal("GetItemByName returned nil")
	}

	if byName.Details.ItemID != item.Details.ItemID {
		t.Errorf("Expected item ID %d, got %d", item.Details.ItemID, byName.Details.ItemID)
	}

	// Get non-existent item
	nonExistent := masterList.GetItem(99999)
	if nonExistent != nil {
		t.Error("GetItem should return nil for non-existent item")
	}

	// Test stats
	stats := masterList.GetStats()
	if stats.TotalItems != 1 {
		t.Errorf("Expected 1 total item, got %d", stats.TotalItems)
	}
}

func TestMasterItemListStatMapping(t *testing.T) {
	masterList := NewMasterItemList()

	// Test getting stat ID by name
	strID := masterList.GetItemStatIDByName("strength")
	if strID == 0 {
		t.Error("Should find strength stat ID")
	}

	// Test getting stat name by ID
	strName := masterList.GetItemStatNameByID(ItemStatStr)
	if strName == "" {
		t.Error("Should find stat name for STR")
	}

	// Add custom stat mapping
	masterList.AddMappedItemStat(9999, "custom stat")

	customID := masterList.GetItemStatIDByName("custom stat")
	if customID != 9999 {
		t.Errorf("Expected custom stat ID 9999, got %d", customID)
	}

	customName := masterList.GetItemStatNameByID(9999)
	if customName != "custom stat" {
		t.Errorf("Expected 'custom stat', got '%s'", customName)
	}
}

func TestPlayerItemList(t *testing.T) {
	playerList := NewPlayerItemList()
	if playerList == nil {
		t.Fatal("NewPlayerItemList returned nil")
	}

	// Initial state
	if playerList.GetNumberOfItems() != 0 {
		t.Error("New player list should be empty")
	}

	// Create test item
	item := NewItem()
	item.Name = "Player Item"
	item.Details.ItemID = 1001
	item.Details.BagID = 0
	item.Details.SlotID = 0

	// Add item
	if !playerList.AddItem(item) {
		t.Error("Should be able to add item")
	}

	if playerList.GetNumberOfItems() != 1 {
		t.Errorf("Expected 1 item, got %d", playerList.GetNumberOfItems())
	}

	// Get item
	retrieved := playerList.GetItem(0, 0, BaseEquipment)
	if retrieved == nil {
		t.Fatal("GetItem returned nil")
	}

	if retrieved.Name != item.Name {
		t.Errorf("Expected name %s, got %s", item.Name, retrieved.Name)
	}

	// Test HasItem
	if !playerList.HasItem(1001, false) {
		t.Error("Player should have item 1001")
	}

	if playerList.HasItem(9999, false) {
		t.Error("Player should not have item 9999")
	}

	// Remove item
	playerList.RemoveItem(item, true, true)

	if playerList.GetNumberOfItems() != 0 {
		t.Errorf("Expected 0 items after removal, got %d", playerList.GetNumberOfItems())
	}
}

func TestPlayerItemListOverflow(t *testing.T) {
	playerList := NewPlayerItemList()

	// Add item to overflow
	item := NewItem()
	item.Name = "Overflow Item"
	item.Details.ItemID = 2001

	if !playerList.AddOverflowItem(item) {
		t.Error("Should be able to add overflow item")
	}

	// Check overflow
	overflowItem := playerList.GetOverflowItem()
	if overflowItem == nil {
		t.Fatal("GetOverflowItem returned nil")
	}

	if overflowItem.Name != item.Name {
		t.Errorf("Expected name %s, got %s", item.Name, overflowItem.Name)
	}

	// Get all overflow items
	overflowItems := playerList.GetOverflowItemList()
	if len(overflowItems) != 1 {
		t.Errorf("Expected 1 overflow item, got %d", len(overflowItems))
	}

	// Remove overflow item
	playerList.RemoveOverflowItem(item)

	overflowItems = playerList.GetOverflowItemList()
	if len(overflowItems) != 0 {
		t.Errorf("Expected 0 overflow items, got %d", len(overflowItems))
	}
}

func TestEquipmentItemList(t *testing.T) {
	equipment := NewEquipmentItemList()
	if equipment == nil {
		t.Fatal("NewEquipmentItemList returned nil")
	}

	// Initial state
	if equipment.GetNumberOfItems() != 0 {
		t.Error("New equipment list should be empty")
	}

	// Create test weapon
	weapon := NewItem()
	weapon.Name = "Test Sword"
	weapon.Details.ItemID = 3001
	weapon.GenericInfo.ItemType = ItemTypeWeapon
	weapon.AddSlot(EQ2PrimarySlot)

	// Equip weapon
	if !equipment.AddItem(EQ2PrimarySlot, weapon) {
		t.Error("Should be able to equip weapon")
	}

	if equipment.GetNumberOfItems() != 1 {
		t.Errorf("Expected 1 equipped item, got %d", equipment.GetNumberOfItems())
	}

	// Get equipped weapon
	equippedWeapon := equipment.GetItem(EQ2PrimarySlot)
	if equippedWeapon == nil {
		t.Fatal("GetItem returned nil")
	}

	if equippedWeapon.Name != weapon.Name {
		t.Errorf("Expected name %s, got %s", weapon.Name, equippedWeapon.Name)
	}

	// Test equipment queries
	if !equipment.HasItem(3001) {
		t.Error("Equipment should have item 3001")
	}

	if !equipment.HasWeaponEquipped() {
		t.Error("Equipment should have weapon equipped")
	}

	weapons := equipment.GetWeapons()
	if len(weapons) != 1 {
		t.Errorf("Expected 1 weapon, got %d", len(weapons))
	}

	// Remove weapon
	equipment.RemoveItem(EQ2PrimarySlot, false)

	if equipment.GetNumberOfItems() != 0 {
		t.Errorf("Expected 0 items after removal, got %d", equipment.GetNumberOfItems())
	}
}

func TestEquipmentValidation(t *testing.T) {
	equipment := NewEquipmentItemList()

	// Create invalid item (no name)
	invalidItem := NewItem()
	invalidItem.Details.ItemID = 4001
	invalidItem.AddSlot(EQ2HeadSlot)

	equipment.SetItem(EQ2HeadSlot, invalidItem, false)

	result := equipment.ValidateEquipment()
	if result.Valid {
		t.Error("Equipment with invalid item should fail validation")
	}

	// Create item that can't be equipped in the slot
	wrongSlotItem := NewItem()
	wrongSlotItem.Name = "Wrong Slot Item"
	wrongSlotItem.Details.ItemID = 4002
	wrongSlotItem.AddSlot(EQ2ChestSlot) // Can only go in chest

	equipment.SetItem(EQ2HeadSlot, wrongSlotItem, false)

	result = equipment.ValidateEquipment()
	if result.Valid {
		t.Error("Equipment with wrong slot item should fail validation")
	}
}

func TestItemSystemAdapter(t *testing.T) {
	// Create dependencies
	masterList := NewMasterItemList()
	spellManager := NewMockSpellManager()

	// Add a test spell
	spellManager.AddMockSpell(1001, "Test Spell", 100, 1, "A test spell")

	adapter := NewItemSystemAdapter(
		masterList,
		spellManager,
		nil, // playerManager
		nil, // packetManager
		nil, // ruleManager
		nil, // databaseService
		nil, // questManager
		nil, // brokerManager
		nil, // craftingManager
		nil, // housingManager
		nil, // lootManager
	)

	if adapter == nil {
		t.Fatal("NewItemSystemAdapter returned nil")
	}

	// Test stats
	stats := adapter.GetSystemStats()
	if stats == nil {
		t.Error("GetSystemStats should not return nil")
	}

	totalTemplates, ok := stats["total_item_templates"].(int32)
	if !ok || totalTemplates != 0 {
		t.Errorf("Expected 0 total templates, got %v", stats["total_item_templates"])
	}
}

func TestItemBrokerChecks(t *testing.T) {
	masterList := NewMasterItemList()

	// Create weapon
	weapon := NewItem()
	weapon.Name = "Test Weapon"
	weapon.GenericInfo.ItemType = ItemTypeWeapon
	weapon.AddSlot(EQ2PrimarySlot)

	// Test broker type checks
	if !masterList.ShouldAddItemBrokerSlot(weapon, ItemBrokerSlotPrimary) {
		t.Error("Weapon should match primary slot broker type")
	}

	if masterList.ShouldAddItemBrokerSlot(weapon, ItemBrokerSlotHead) {
		t.Error("Weapon should not match head slot broker type")
	}

	// Create armor with stats
	armor := NewItem()
	armor.Name = "Test Armor"
	armor.GenericInfo.ItemType = ItemTypeArmor
	armor.AddStat(&ItemStat{
		StatName: "Strength",
		StatType: ItemStatStr,
		Value:    10,
	})

	if !masterList.ShouldAddItemBrokerStat(armor, ItemBrokerStatTypeStr) {
		t.Error("Armor should match STR stat broker type")
	}

	if masterList.ShouldAddItemBrokerStat(armor, ItemBrokerStatTypeInt) {
		t.Error("Armor should not match INT stat broker type")
	}
}

func TestItemSearchCriteria(t *testing.T) {
	masterList := NewMasterItemList()

	// Add test items
	sword := NewItem()
	sword.Name = "Steel Sword"
	sword.Details.ItemID = 5001
	sword.Details.Tier = 1
	sword.Details.RecommendedLevel = 10
	sword.GenericInfo.ItemType = ItemTypeWeapon
	sword.BrokerPrice = 1000

	armor := NewItem()
	armor.Name = "Iron Armor"
	armor.Details.ItemID = 5002
	armor.Details.Tier = 2
	armor.Details.RecommendedLevel = 15
	armor.GenericInfo.ItemType = ItemTypeArmor
	armor.BrokerPrice = 2000

	masterList.AddItem(sword)
	masterList.AddItem(armor)

	// Search by name
	criteria := &ItemSearchCriteria{
		Name: "sword",
	}

	results := masterList.GetItems(criteria)
	if len(results) != 1 {
		t.Errorf("Expected 1 result for sword search, got %d", len(results))
	}

	if results[0].Name != sword.Name {
		t.Errorf("Expected %s, got %s", sword.Name, results[0].Name)
	}

	// Search by price range
	criteria = &ItemSearchCriteria{
		MinPrice: 1500,
		MaxPrice: 2500,
	}

	results = masterList.GetItems(criteria)
	if len(results) != 1 {
		t.Errorf("Expected 1 result for price search, got %d", len(results))
	}

	if results[0].Name != armor.Name {
		t.Errorf("Expected %s, got %s", armor.Name, results[0].Name)
	}

	// Search by tier
	criteria = &ItemSearchCriteria{
		MinTier: 2,
		MaxTier: 2,
	}

	results = masterList.GetItems(criteria)
	if len(results) != 1 {
		t.Errorf("Expected 1 result for tier search, got %d", len(results))
	}

	// Search with no matches
	criteria = &ItemSearchCriteria{
		Name: "nonexistent",
	}

	results = masterList.GetItems(criteria)
	if len(results) != 0 {
		t.Errorf("Expected 0 results for nonexistent search, got %d", len(results))
	}
}

func TestNextUniqueID(t *testing.T) {
	id1 := NextUniqueID()
	id2 := NextUniqueID()

	if id1 == id2 {
		t.Error("NextUniqueID should return different IDs")
	}

	if id2 != id1+1 {
		t.Errorf("Expected ID2 to be ID1+1, got %d and %d", id1, id2)
	}
}

func TestItemError(t *testing.T) {
	err := NewItemError("test error")
	if err == nil {
		t.Fatal("NewItemError returned nil")
	}

	if err.Error() != "test error" {
		t.Errorf("Expected 'test error', got '%s'", err.Error())
	}

	if !IsItemError(err) {
		t.Error("Should identify as item error")
	}

	// Test with non-item error
	if IsItemError(fmt.Errorf("not an item error")) {
		t.Error("Should not identify as item error")
	}
}

func TestConstants(t *testing.T) {
	// Test slot constants
	if EQ2PrimarySlot != 0 {
		t.Errorf("Expected EQ2PrimarySlot to be 0, got %d", EQ2PrimarySlot)
	}

	if NumSlots != 25 {
		t.Errorf("Expected NumSlots to be 25, got %d", NumSlots)
	}

	// Test item type constants
	if ItemTypeWeapon != 1 {
		t.Errorf("Expected ItemTypeWeapon to be 1, got %d", ItemTypeWeapon)
	}

	// Test flag constants
	if Attuned != 1 {
		t.Errorf("Expected Attuned to be 1, got %d", Attuned)
	}

	// Test stat constants
	if ItemStatStr != 0 {
		t.Errorf("Expected ItemStatStr to be 0, got %d", ItemStatStr)
	}
}

func BenchmarkItemCreation(b *testing.B) {
	for i := 0; i < b.N; i++ {
		item := NewItem()
		item.Name = "Benchmark Item"
		item.Details.ItemID = int32(i)
	}
}

func BenchmarkMasterItemListAccess(b *testing.B) {
	masterList := NewMasterItemList()

	// Add test items
	for i := 0; i < 1000; i++ {
		item := NewItem()
		item.Name = fmt.Sprintf("Item %d", i)
		item.Details.ItemID = int32(i + 1000)
		masterList.AddItem(item)
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		masterList.GetItem(int32((i % 1000) + 1000))
	}
}

func BenchmarkPlayerItemListAdd(b *testing.B) {
	playerList := NewPlayerItemList()

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		item := NewItem()
		item.Name = fmt.Sprintf("Item %d", i)
		item.Details.ItemID = int32(i)
		item.Details.BagID = int32(i % 6)
		item.Details.SlotID = int16(i % 20)
		playerList.AddItem(item)
	}
}

func BenchmarkEquipmentBonusCalculation(b *testing.B) {
	equipment := NewEquipmentItemList()

	// Add some equipped items with stats
	for slot := 0; slot < 10; slot++ {
		item := NewItem()
		item.Name = fmt.Sprintf("Equipment %d", slot)
		item.Details.ItemID = int32(slot + 6000)
		item.AddSlot(int8(slot))

		// Add some stats
		item.AddStat(&ItemStat{StatType: ItemStatStr, Value: 10})
		item.AddStat(&ItemStat{StatType: ItemStatAgi, Value: 5})
		item.AddStat(&ItemStat{StatType: ItemStatHealth, Value: 100})

		equipment.SetItem(int8(slot), item, false)
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		equipment.CalculateEquipmentBonuses()
	}
}
