package items

import (
	"database/sql"
	"testing"

	_ "zombiezen.com/go/sqlite"
)

// setupTestDB creates a test database with minimal schema
func setupTestDB(t *testing.T) *sql.DB {
	db, err := sql.Open("sqlite", ":memory:")
	if err != nil {
		t.Fatalf("Failed to open test database: %v", err)
	}

	// Create minimal test schema
	schema := `
	CREATE TABLE items (
		id INTEGER PRIMARY KEY,
		soe_id INTEGER DEFAULT 0,
		name TEXT NOT NULL,
		description TEXT DEFAULT '',
		icon INTEGER DEFAULT 0,
		icon2 INTEGER DEFAULT 0,
		icon_heroic_op INTEGER DEFAULT 0,
		icon_heroic_op2 INTEGER DEFAULT 0,
		icon_id INTEGER DEFAULT 0,
		icon_backdrop INTEGER DEFAULT 0,
		icon_border INTEGER DEFAULT 0,
		icon_tint_red INTEGER DEFAULT 0,
		icon_tint_green INTEGER DEFAULT 0,
		icon_tint_blue INTEGER DEFAULT 0,
		tier INTEGER DEFAULT 1,
		level INTEGER DEFAULT 1,
		success_sellback INTEGER DEFAULT 0,
		stack_size INTEGER DEFAULT 1,
		generic_info_show_name INTEGER DEFAULT 1,
		generic_info_item_flags INTEGER DEFAULT 0,
		generic_info_item_flags2 INTEGER DEFAULT 0,
		generic_info_creator_flag INTEGER DEFAULT 0,
		generic_info_condition INTEGER DEFAULT 100,
		generic_info_weight INTEGER DEFAULT 10,
		generic_info_skill_req1 INTEGER DEFAULT 0,
		generic_info_skill_req2 INTEGER DEFAULT 0,
		generic_info_skill_min_level INTEGER DEFAULT 0,
		generic_info_item_type INTEGER DEFAULT 0,
		generic_info_appearance_id INTEGER DEFAULT 0,
		generic_info_appearance_red INTEGER DEFAULT 0,
		generic_info_appearance_green INTEGER DEFAULT 0,
		generic_info_appearance_blue INTEGER DEFAULT 0,
		generic_info_appearance_highlight_red INTEGER DEFAULT 0,
		generic_info_appearance_highlight_green INTEGER DEFAULT 0,
		generic_info_appearance_highlight_blue INTEGER DEFAULT 0,
		generic_info_collectable INTEGER DEFAULT 0,
		generic_info_offers_quest_id INTEGER DEFAULT 0,
		generic_info_part_of_quest_id INTEGER DEFAULT 0,
		generic_info_max_charges INTEGER DEFAULT 0,
		generic_info_adventure_classes INTEGER DEFAULT 0,
		generic_info_tradeskill_classes INTEGER DEFAULT 0,
		generic_info_adventure_default_level INTEGER DEFAULT 1,
		generic_info_tradeskill_default_level INTEGER DEFAULT 1,
		generic_info_usable INTEGER DEFAULT 0,
		generic_info_harvest INTEGER DEFAULT 0,
		generic_info_body_drop INTEGER DEFAULT 0,
		generic_info_pvp_description INTEGER DEFAULT 0,
		generic_info_merc_only INTEGER DEFAULT 0,
		generic_info_mount_only INTEGER DEFAULT 0,
		generic_info_set_id INTEGER DEFAULT 0,
		generic_info_collectable_unk INTEGER DEFAULT 0,
		generic_info_transmuted_material INTEGER DEFAULT 0,
		broker_price INTEGER DEFAULT 0,
		sell_price INTEGER DEFAULT 0,
		max_sell_value INTEGER DEFAULT 0,
		created TEXT DEFAULT CURRENT_TIMESTAMP,
		script_name TEXT DEFAULT '',
		lua_script TEXT DEFAULT ''
	);

	CREATE TABLE item_mod_stats (
		item_id INTEGER,
		stat_type INTEGER,
		stat_subtype INTEGER DEFAULT 0,
		value REAL,
		stat_name TEXT DEFAULT '',
		level INTEGER DEFAULT 0
	);

	CREATE TABLE item_effects (
		item_id INTEGER,
		effect TEXT,
		percentage INTEGER DEFAULT 0,
		subbulletflag INTEGER DEFAULT 0
	);

	CREATE TABLE item_appearances (
		item_id INTEGER,
		type INTEGER,
		red INTEGER DEFAULT 0,
		green INTEGER DEFAULT 0,
		blue INTEGER DEFAULT 0,
		highlight_red INTEGER DEFAULT 0,
		highlight_green INTEGER DEFAULT 0,
		highlight_blue INTEGER DEFAULT 0
	);

	CREATE TABLE item_levels_override (
		item_id INTEGER,
		adventure_class INTEGER,
		tradeskill_class INTEGER,
		level INTEGER
	);

	CREATE TABLE item_mod_strings (
		item_id INTEGER,
		stat_string TEXT
	);

	CREATE TABLE character_items (
		char_id INTEGER,
		item_id INTEGER,
		unique_id INTEGER PRIMARY KEY,
		inv_slot_id INTEGER,
		slot_id INTEGER,
		appearance_type INTEGER DEFAULT 0,
		icon INTEGER DEFAULT 0,
		icon2 INTEGER DEFAULT 0,
		count INTEGER DEFAULT 1,
		tier INTEGER DEFAULT 1,
		bag_id INTEGER DEFAULT 0,
		details_count INTEGER DEFAULT 1,
		creator TEXT DEFAULT '',
		adornment_slot0 INTEGER DEFAULT 0,
		adornment_slot1 INTEGER DEFAULT 0,
		adornment_slot2 INTEGER DEFAULT 0,
		group_id INTEGER DEFAULT 0,
		creator_app TEXT DEFAULT '',
		random_seed INTEGER DEFAULT 0,
		created TEXT DEFAULT CURRENT_TIMESTAMP
	);

	CREATE TABLE item_details_weapon (
		item_id INTEGER PRIMARY KEY,
		wield_type INTEGER DEFAULT 2,
		damage_low1 INTEGER DEFAULT 1,
		damage_high1 INTEGER DEFAULT 2,
		damage_low2 INTEGER DEFAULT 0,
		damage_high2 INTEGER DEFAULT 0,
		damage_low3 INTEGER DEFAULT 0,
		damage_high3 INTEGER DEFAULT 0,
		delay_hundredths INTEGER DEFAULT 300,
		rating REAL DEFAULT 1.0
	);

	CREATE TABLE item_details_armor (
		item_id INTEGER PRIMARY KEY,
		mitigation_low INTEGER DEFAULT 1,
		mitigation_high INTEGER DEFAULT 2
	);

	CREATE TABLE item_details_bag (
		item_id INTEGER PRIMARY KEY,
		num_slots INTEGER DEFAULT 6,
		weight_reduction INTEGER DEFAULT 0
	);
	`

	if _, err := db.Exec(schema); err != nil {
		t.Fatalf("Failed to create test schema: %v", err)
	}

	return db
}

// insertTestItem inserts a test item into the database
func insertTestItem(t *testing.T, db *sql.DB, itemID int32, name string, itemType int8) {
	query := `
		INSERT INTO items (id, name, generic_info_item_type)
		VALUES (?, ?, ?)
	`
	_, err := db.Exec(query, itemID, name, itemType)
	if err != nil {
		t.Fatalf("Failed to insert test item: %v", err)
	}
}

func TestNewItemDatabase(t *testing.T) {
	db := setupTestDB(t)
	defer db.Close()

	idb := NewItemDatabase(db)
	if idb == nil {
		t.Fatal("Expected non-nil ItemDatabase")
	}

	if idb.db != db {
		t.Error("Expected database connection to be set")
	}

	if len(idb.queries) == 0 {
		t.Error("Expected queries to be prepared")
	}

	if len(idb.loadedItems) != 0 {
		t.Error("Expected loadedItems to be empty initially")
	}
}

func TestLoadItems(t *testing.T) {
	db := setupTestDB(t)
	defer db.Close()

	// Insert test items
	insertTestItem(t, db, 1, "Test Sword", ItemTypeWeapon)
	insertTestItem(t, db, 2, "Test Armor", ItemTypeArmor)
	insertTestItem(t, db, 3, "Test Bag", ItemTypeBag)

	// Add weapon details for sword
	_, err := db.Exec(`
		INSERT INTO item_details_weapon (item_id, damage_low1, damage_high1, delay_hundredths)
		VALUES (1, 10, 15, 250)
	`)
	if err != nil {
		t.Fatalf("Failed to insert weapon details: %v", err)
	}

	// Add armor details
	_, err = db.Exec(`
		INSERT INTO item_details_armor (item_id, mitigation_low, mitigation_high)
		VALUES (2, 5, 8)
	`)
	if err != nil {
		t.Fatalf("Failed to insert armor details: %v", err)
	}

	// Add bag details
	_, err = db.Exec(`
		INSERT INTO item_details_bag (item_id, num_slots, weight_reduction)
		VALUES (3, 6, 10)
	`)
	if err != nil {
		t.Fatalf("Failed to insert bag details: %v", err)
	}

	idb := NewItemDatabase(db)
	masterList := NewMasterItemList()

	err = idb.LoadItems(masterList)
	if err != nil {
		t.Fatalf("Failed to load items: %v", err)
	}

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

	// Test specific items
	sword := masterList.GetItem(1)
	if sword == nil {
		t.Fatal("Expected to find sword item")
	}
	if sword.Name != "Test Sword" {
		t.Errorf("Expected sword name 'Test Sword', got '%s'", sword.Name)
	}
	if sword.WeaponInfo == nil {
		t.Error("Expected weapon info to be loaded")
	} else {
		if sword.WeaponInfo.DamageLow1 != 10 {
			t.Errorf("Expected damage low 10, got %d", sword.WeaponInfo.DamageLow1)
		}
	}

	armor := masterList.GetItem(2)
	if armor == nil {
		t.Fatal("Expected to find armor item")
	}
	if armor.ArmorInfo == nil {
		t.Error("Expected armor info to be loaded")
	}

	bag := masterList.GetItem(3)
	if bag == nil {
		t.Fatal("Expected to find bag item")
	}
	if bag.BagInfo == nil {
		t.Error("Expected bag info to be loaded")
	}
}

func TestLoadItemStats(t *testing.T) {
	db := setupTestDB(t)
	defer db.Close()

	// Insert test item
	insertTestItem(t, db, 1, "Test Item", ItemTypeNormal)

	// Add item stats
	_, err := db.Exec(`
		INSERT INTO item_mod_stats (item_id, stat_type, stat_subtype, value, stat_name)
		VALUES (1, 0, 0, 10.0, 'Strength')
	`)
	if err != nil {
		t.Fatalf("Failed to insert item stats: %v", err)
	}

	idb := NewItemDatabase(db)
	masterList := NewMasterItemList()

	err = idb.LoadItems(masterList)
	if err != nil {
		t.Fatalf("Failed to load items: %v", err)
	}

	item := masterList.GetItem(1)
	if item == nil {
		t.Fatal("Expected to find item")
	}

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

	if item.ItemStats[0].StatName != "Strength" {
		t.Errorf("Expected stat name 'Strength', got '%s'", item.ItemStats[0].StatName)
	}

	if item.ItemStats[0].Value != 10.0 {
		t.Errorf("Expected stat value 10.0, got %f", item.ItemStats[0].Value)
	}
}

func TestSaveAndLoadCharacterItems(t *testing.T) {
	db := setupTestDB(t)
	defer db.Close()

	// Insert test item template
	insertTestItem(t, db, 1, "Test Item", ItemTypeNormal)

	idb := NewItemDatabase(db)
	masterList := NewMasterItemList()

	// Load item templates
	err := idb.LoadItems(masterList)
	if err != nil {
		t.Fatalf("Failed to load items: %v", err)
	}

	// Create test character items
	inventory := NewPlayerItemList()
	equipment := NewEquipmentItemList()

	// Create an item instance
	template := masterList.GetItem(1)
	if template == nil {
		t.Fatal("Expected to find item template")
	}

	item := NewItemFromTemplate(template)
	item.Details.InvSlotID = 1000 // Inventory slot
	item.Details.Count = 5

	inventory.AddItem(item)

	// Save character items
	charID := uint32(123)
	err = idb.SaveCharacterItems(charID, inventory, equipment)
	if err != nil {
		t.Fatalf("Failed to save character items: %v", err)
	}

	// Load character items
	loadedInventory, loadedEquipment, err := idb.LoadCharacterItems(charID, masterList)
	if err != nil {
		t.Fatalf("Failed to load character items: %v", err)
	}

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

	if loadedEquipment.GetNumberOfItems() != 0 {
		t.Errorf("Expected 0 equipped items, got %d", loadedEquipment.GetNumberOfItems())
	}

	// Verify item properties
	allItems := loadedInventory.GetAllItems()
	if len(allItems) != 1 {
		t.Fatalf("Expected 1 item in all items, got %d", len(allItems))
	}

	loadedItem := allItems[int32(item.Details.UniqueID)]
	if loadedItem == nil {
		t.Fatal("Expected to find loaded item")
	}

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

	if loadedItem.Name != "Test Item" {
		t.Errorf("Expected item name 'Test Item', got '%s'", loadedItem.Name)
	}
}

func TestDeleteCharacterItem(t *testing.T) {
	db := setupTestDB(t)
	defer db.Close()

	// Insert test character item directly
	charID := uint32(123)
	uniqueID := int64(456)
	_, err := db.Exec(`
		INSERT INTO character_items (char_id, item_id, unique_id, inv_slot_id, slot_id, count)
		VALUES (?, 1, ?, 1000, 0, 1)
	`, charID, uniqueID)
	if err != nil {
		t.Fatalf("Failed to insert test character item: %v", err)
	}

	idb := NewItemDatabase(db)

	// Delete the item
	err = idb.DeleteCharacterItem(charID, uniqueID)
	if err != nil {
		t.Fatalf("Failed to delete character item: %v", err)
	}

	// Verify item was deleted
	var count int
	err = db.QueryRow("SELECT COUNT(*) FROM character_items WHERE char_id = ? AND unique_id = ?", charID, uniqueID).Scan(&count)
	if err != nil {
		t.Fatalf("Failed to query character items: %v", err)
	}

	if count != 0 {
		t.Errorf("Expected 0 items after deletion, got %d", count)
	}
}

func TestGetCharacterItemCount(t *testing.T) {
	db := setupTestDB(t)
	defer db.Close()

	charID := uint32(123)
	idb := NewItemDatabase(db)

	// Initially should be 0
	count, err := idb.GetCharacterItemCount(charID)
	if err != nil {
		t.Fatalf("Failed to get character item count: %v", err)
	}
	if count != 0 {
		t.Errorf("Expected 0 items initially, got %d", count)
	}

	// Insert test items
	for i := 0; i < 3; i++ {
		_, err := db.Exec(`
			INSERT INTO character_items (char_id, item_id, unique_id, inv_slot_id, slot_id, count)
			VALUES (?, 1, ?, 1000, 0, 1)
		`, charID, i+1)
		if err != nil {
			t.Fatalf("Failed to insert test character item: %v", err)
		}
	}

	// Should now be 3
	count, err = idb.GetCharacterItemCount(charID)
	if err != nil {
		t.Fatalf("Failed to get character item count: %v", err)
	}
	if count != 3 {
		t.Errorf("Expected 3 items, got %d", count)
	}
}

func TestNextUniqueItemID(t *testing.T) {
	id1 := NextUniqueItemID()
	id2 := NextUniqueItemID()

	if id1 >= id2 {
		t.Errorf("Expected unique IDs to be increasing, got %d and %d", id1, id2)
	}

	if id1 == id2 {
		t.Error("Expected unique IDs to be different")
	}
}

func TestItemDatabaseClose(t *testing.T) {
	db := setupTestDB(t)
	defer db.Close()

	idb := NewItemDatabase(db)

	// Should not error when closing
	err := idb.Close()
	if err != nil {
		t.Errorf("Expected no error when closing, got: %v", err)
	}
}