package loot

import (
	"database/sql"
	"fmt"
	"log"
	"sync"
	"time"
)

// LootDatabase handles all database operations for the loot system
type LootDatabase struct {
	db            *sql.DB
	queries       map[string]*sql.Stmt
	lootTables    map[int32]*LootTable
	spawnLoot     map[int32][]int32        // spawn_id -> []loot_table_id
	globalLoot    []*GlobalLoot
	mutex         sync.RWMutex
}

// NewLootDatabase creates a new loot database manager
func NewLootDatabase(db *sql.DB) *LootDatabase {
	ldb := &LootDatabase{
		db:         db,
		queries:    make(map[string]*sql.Stmt),
		lootTables: make(map[int32]*LootTable),
		spawnLoot:  make(map[int32][]int32),
		globalLoot: make([]*GlobalLoot, 0),
	}

	// Prepare commonly used queries
	ldb.prepareQueries()

	return ldb
}

// prepareQueries prepares all commonly used SQL queries
func (ldb *LootDatabase) prepareQueries() {
	queries := map[string]string{
		"load_loot_tables": `
			SELECT id, name, mincoin, maxcoin, maxlootitems, lootdrop_probability, coin_probability
			FROM loottable
			ORDER BY id
		`,
		
		"load_loot_drops": `
			SELECT loot_table_id, item_id, item_charges, equip_item, probability, no_drop_quest_completed_id
			FROM lootdrop
			WHERE loot_table_id = ?
			ORDER BY probability DESC
		`,
		
		"load_spawn_loot": `
			SELECT spawn_id, loottable_id
			FROM spawn_loot
			ORDER BY spawn_id
		`,
		
		"load_global_loot": `
			SELECT type, loot_table, value1, value2, value3, value4
			FROM loot_global
			ORDER BY type, value1
		`,
		
		"insert_loot_table": `
			INSERT INTO loottable (id, name, mincoin, maxcoin, maxlootitems, lootdrop_probability, coin_probability)
			VALUES (?, ?, ?, ?, ?, ?, ?)
		`,
		
		"update_loot_table": `
			UPDATE loottable 
			SET name = ?, mincoin = ?, maxcoin = ?, maxlootitems = ?, lootdrop_probability = ?, coin_probability = ?
			WHERE id = ?
		`,
		
		"delete_loot_table": `
			DELETE FROM loottable WHERE id = ?
		`,
		
		"insert_loot_drop": `
			INSERT INTO lootdrop (loot_table_id, item_id, item_charges, equip_item, probability, no_drop_quest_completed_id)
			VALUES (?, ?, ?, ?, ?, ?)
		`,
		
		"delete_loot_drops": `
			DELETE FROM lootdrop WHERE loot_table_id = ?
		`,
		
		"insert_spawn_loot": `
			INSERT OR REPLACE INTO spawn_loot (spawn_id, loottable_id)
			VALUES (?, ?)
		`,
		
		"delete_spawn_loot": `
			DELETE FROM spawn_loot WHERE spawn_id = ?
		`,
		
		"insert_global_loot": `
			INSERT INTO loot_global (type, loot_table, value1, value2, value3, value4)
			VALUES (?, ?, ?, ?, ?, ?)
		`,
		
		"delete_global_loot": `
			DELETE FROM loot_global WHERE type = ?
		`,
		
		"get_loot_table": `
			SELECT id, name, mincoin, maxcoin, maxlootitems, lootdrop_probability, coin_probability
			FROM loottable
			WHERE id = ?
		`,
		
		"get_spawn_loot_tables": `
			SELECT loottable_id
			FROM spawn_loot
			WHERE spawn_id = ?
		`,
		
		"count_loot_tables": `
			SELECT COUNT(*) FROM loottable
		`,
		
		"count_loot_drops": `
			SELECT COUNT(*) FROM lootdrop
		`,
		
		"count_spawn_loot": `
			SELECT COUNT(*) FROM spawn_loot
		`,
	}

	for name, query := range queries {
		if stmt, err := ldb.db.Prepare(query); err != nil {
			log.Printf("%s Failed to prepare query %s: %v", LogPrefixDatabase, name, err)
		} else {
			ldb.queries[name] = stmt
		}
	}
}

// LoadAllLootData loads all loot data from the database
func (ldb *LootDatabase) LoadAllLootData() error {
	log.Printf("%s Loading loot data from database...", LogPrefixDatabase)
	
	// Load loot tables first
	if err := ldb.loadLootTables(); err != nil {
		return fmt.Errorf("failed to load loot tables: %v", err)
	}
	
	// Load loot drops for each table
	if err := ldb.loadLootDrops(); err != nil {
		return fmt.Errorf("failed to load loot drops: %v", err)
	}
	
	// Load spawn loot assignments
	if err := ldb.loadSpawnLoot(); err != nil {
		return fmt.Errorf("failed to load spawn loot: %v", err)
	}
	
	// Load global loot configuration
	if err := ldb.loadGlobalLoot(); err != nil {
		return fmt.Errorf("failed to load global loot: %v", err)
	}
	
	ldb.mutex.RLock()
	tableCount := len(ldb.lootTables)
	spawnCount := len(ldb.spawnLoot)
	globalCount := len(ldb.globalLoot)
	ldb.mutex.RUnlock()
	
	log.Printf("%s Loaded %d loot tables, %d spawn assignments, %d global loot entries", 
		LogPrefixDatabase, tableCount, spawnCount, globalCount)
	
	return nil
}

// loadLootTables loads all loot tables from the database
func (ldb *LootDatabase) loadLootTables() error {
	stmt := ldb.queries["load_loot_tables"]
	if stmt == nil {
		return fmt.Errorf("load_loot_tables query not prepared")
	}

	rows, err := stmt.Query()
	if err != nil {
		return fmt.Errorf("failed to query loot tables: %v", err)
	}
	defer rows.Close()

	ldb.mutex.Lock()
	defer ldb.mutex.Unlock()
	
	// Clear existing tables
	ldb.lootTables = make(map[int32]*LootTable)

	for rows.Next() {
		table := &LootTable{
			Drops: make([]*LootDrop, 0),
		}

		err := rows.Scan(
			&table.ID,
			&table.Name,
			&table.MinCoin,
			&table.MaxCoin,
			&table.MaxLootItems,
			&table.LootDropProbability,
			&table.CoinProbability,
		)

		if err != nil {
			log.Printf("%s Error scanning loot table row: %v", LogPrefixDatabase, err)
			continue
		}

		ldb.lootTables[table.ID] = table
	}

	return rows.Err()
}

// loadLootDrops loads all loot drops for the loaded loot tables
func (ldb *LootDatabase) loadLootDrops() error {
	stmt := ldb.queries["load_loot_drops"]
	if stmt == nil {
		return fmt.Errorf("load_loot_drops query not prepared")
	}

	ldb.mutex.Lock()
	defer ldb.mutex.Unlock()

	for tableID, table := range ldb.lootTables {
		rows, err := stmt.Query(tableID)
		if err != nil {
			log.Printf("%s Failed to query loot drops for table %d: %v", LogPrefixDatabase, tableID, err)
			continue
		}

		for rows.Next() {
			drop := &LootDrop{}
			var equipItem int8

			err := rows.Scan(
				&drop.LootTableID,
				&drop.ItemID,
				&drop.ItemCharges,
				&equipItem,
				&drop.Probability,
				&drop.NoDropQuestCompletedID,
			)

			if err != nil {
				log.Printf("%s Error scanning loot drop row: %v", LogPrefixDatabase, err)
				continue
			}

			drop.EquipItem = equipItem == 1
			table.Drops = append(table.Drops, drop)
		}

		rows.Close()
	}

	return nil
}

// loadSpawnLoot loads spawn to loot table assignments
func (ldb *LootDatabase) loadSpawnLoot() error {
	stmt := ldb.queries["load_spawn_loot"]
	if stmt == nil {
		return fmt.Errorf("load_spawn_loot query not prepared")
	}

	rows, err := stmt.Query()
	if err != nil {
		return fmt.Errorf("failed to query spawn loot: %v", err)
	}
	defer rows.Close()

	ldb.mutex.Lock()
	defer ldb.mutex.Unlock()
	
	// Clear existing spawn loot
	ldb.spawnLoot = make(map[int32][]int32)

	for rows.Next() {
		var spawnID, lootTableID int32

		err := rows.Scan(&spawnID, &lootTableID)
		if err != nil {
			log.Printf("%s Error scanning spawn loot row: %v", LogPrefixDatabase, err)
			continue
		}

		ldb.spawnLoot[spawnID] = append(ldb.spawnLoot[spawnID], lootTableID)
	}

	return rows.Err()
}

// loadGlobalLoot loads global loot configuration
func (ldb *LootDatabase) loadGlobalLoot() error {
	stmt := ldb.queries["load_global_loot"]
	if stmt == nil {
		return fmt.Errorf("load_global_loot query not prepared")
	}

	rows, err := stmt.Query()
	if err != nil {
		return fmt.Errorf("failed to query global loot: %v", err)
	}
	defer rows.Close()

	ldb.mutex.Lock()
	defer ldb.mutex.Unlock()
	
	// Clear existing global loot
	ldb.globalLoot = make([]*GlobalLoot, 0)

	for rows.Next() {
		var lootType string
		var tableID, value1, value2, value3, value4 int32

		err := rows.Scan(&lootType, &tableID, &value1, &value2, &value3, &value4)
		if err != nil {
			log.Printf("%s Error scanning global loot row: %v", LogPrefixDatabase, err)
			continue
		}

		global := &GlobalLoot{
			TableID: tableID,
		}

		// Parse loot type and values
		switch lootType {
		case "level":
			global.Type = GlobalLootTypeLevel
			global.MinLevel = int8(value1)
			global.MaxLevel = int8(value2)
			global.LootTier = value3
		case "race":
			global.Type = GlobalLootTypeRace
			global.Race = int16(value1)
			global.LootTier = value2
		case "zone":
			global.Type = GlobalLootTypeZone
			global.ZoneID = value1
			global.LootTier = value2
		default:
			log.Printf("%s Unknown global loot type: %s", LogPrefixDatabase, lootType)
			continue
		}

		ldb.globalLoot = append(ldb.globalLoot, global)
	}

	return rows.Err()
}

// GetLootTable returns a loot table by ID (thread-safe)
func (ldb *LootDatabase) GetLootTable(tableID int32) *LootTable {
	ldb.mutex.RLock()
	defer ldb.mutex.RUnlock()
	
	return ldb.lootTables[tableID]
}

// GetSpawnLootTables returns all loot table IDs for a spawn (thread-safe)
func (ldb *LootDatabase) GetSpawnLootTables(spawnID int32) []int32 {
	ldb.mutex.RLock()
	defer ldb.mutex.RUnlock()
	
	tables := ldb.spawnLoot[spawnID]
	if tables == nil {
		return nil
	}
	
	// Return a copy to prevent external modification
	result := make([]int32, len(tables))
	copy(result, tables)
	return result
}

// GetGlobalLootTables returns applicable global loot tables for given parameters
func (ldb *LootDatabase) GetGlobalLootTables(level int16, race int16, zoneID int32) []*GlobalLoot {
	ldb.mutex.RLock()
	defer ldb.mutex.RUnlock()
	
	var result []*GlobalLoot
	
	for _, global := range ldb.globalLoot {
		switch global.Type {
		case GlobalLootTypeLevel:
			if level >= int16(global.MinLevel) && level <= int16(global.MaxLevel) {
				result = append(result, global)
			}
		case GlobalLootTypeRace:
			if race == global.Race {
				result = append(result, global)
			}
		case GlobalLootTypeZone:
			if zoneID == global.ZoneID {
				result = append(result, global)
			}
		}
	}
	
	return result
}

// AddLootTable adds a new loot table to the database
func (ldb *LootDatabase) AddLootTable(table *LootTable) error {
	stmt := ldb.queries["insert_loot_table"]
	if stmt == nil {
		return fmt.Errorf("insert_loot_table query not prepared")
	}

	_, err := stmt.Exec(
		table.ID,
		table.Name,
		table.MinCoin,
		table.MaxCoin,
		table.MaxLootItems,
		table.LootDropProbability,
		table.CoinProbability,
	)

	if err != nil {
		return fmt.Errorf("failed to insert loot table: %v", err)
	}

	// Add drops if any
	for _, drop := range table.Drops {
		if err := ldb.AddLootDrop(drop); err != nil {
			log.Printf("%s Failed to add loot drop for table %d: %v", LogPrefixDatabase, table.ID, err)
		}
	}

	// Update in-memory cache
	ldb.mutex.Lock()
	ldb.lootTables[table.ID] = table
	ldb.mutex.Unlock()

	log.Printf("%s Added loot table %d (%s) with %d drops", LogPrefixDatabase, table.ID, table.Name, len(table.Drops))
	return nil
}

// AddLootDrop adds a new loot drop to the database
func (ldb *LootDatabase) AddLootDrop(drop *LootDrop) error {
	stmt := ldb.queries["insert_loot_drop"]
	if stmt == nil {
		return fmt.Errorf("insert_loot_drop query not prepared")
	}

	equipItem := int8(0)
	if drop.EquipItem {
		equipItem = 1
	}

	_, err := stmt.Exec(
		drop.LootTableID,
		drop.ItemID,
		drop.ItemCharges,
		equipItem,
		drop.Probability,
		drop.NoDropQuestCompletedID,
	)

	return err
}

// UpdateLootTable updates an existing loot table
func (ldb *LootDatabase) UpdateLootTable(table *LootTable) error {
	stmt := ldb.queries["update_loot_table"]
	if stmt == nil {
		return fmt.Errorf("update_loot_table query not prepared")
	}

	_, err := stmt.Exec(
		table.Name,
		table.MinCoin,
		table.MaxCoin,
		table.MaxLootItems,
		table.LootDropProbability,
		table.CoinProbability,
		table.ID,
	)

	if err != nil {
		return fmt.Errorf("failed to update loot table: %v", err)
	}

	// Update drops - delete old ones and insert new ones
	if err := ldb.DeleteLootDrops(table.ID); err != nil {
		log.Printf("%s Failed to delete old loot drops for table %d: %v", LogPrefixDatabase, table.ID, err)
	}

	for _, drop := range table.Drops {
		if err := ldb.AddLootDrop(drop); err != nil {
			log.Printf("%s Failed to add updated loot drop for table %d: %v", LogPrefixDatabase, table.ID, err)
		}
	}

	// Update in-memory cache
	ldb.mutex.Lock()
	ldb.lootTables[table.ID] = table
	ldb.mutex.Unlock()

	return nil
}

// DeleteLootTable removes a loot table and all its drops
func (ldb *LootDatabase) DeleteLootTable(tableID int32) error {
	// Delete drops first
	if err := ldb.DeleteLootDrops(tableID); err != nil {
		return fmt.Errorf("failed to delete loot drops: %v", err)
	}

	// Delete table
	stmt := ldb.queries["delete_loot_table"]
	if stmt == nil {
		return fmt.Errorf("delete_loot_table query not prepared")
	}

	_, err := stmt.Exec(tableID)
	if err != nil {
		return fmt.Errorf("failed to delete loot table: %v", err)
	}

	// Remove from in-memory cache
	ldb.mutex.Lock()
	delete(ldb.lootTables, tableID)
	ldb.mutex.Unlock()

	return nil
}

// DeleteLootDrops removes all drops for a loot table
func (ldb *LootDatabase) DeleteLootDrops(tableID int32) error {
	stmt := ldb.queries["delete_loot_drops"]
	if stmt == nil {
		return fmt.Errorf("delete_loot_drops query not prepared")
	}

	_, err := stmt.Exec(tableID)
	return err
}

// AddSpawnLoot assigns a loot table to a spawn
func (ldb *LootDatabase) AddSpawnLoot(spawnID, tableID int32) error {
	stmt := ldb.queries["insert_spawn_loot"]
	if stmt == nil {
		return fmt.Errorf("insert_spawn_loot query not prepared")
	}

	_, err := stmt.Exec(spawnID, tableID)
	if err != nil {
		return fmt.Errorf("failed to insert spawn loot: %v", err)
	}

	// Update in-memory cache
	ldb.mutex.Lock()
	ldb.spawnLoot[spawnID] = append(ldb.spawnLoot[spawnID], tableID)
	ldb.mutex.Unlock()

	return nil
}

// DeleteSpawnLoot removes all loot table assignments for a spawn
func (ldb *LootDatabase) DeleteSpawnLoot(spawnID int32) error {
	stmt := ldb.queries["delete_spawn_loot"]
	if stmt == nil {
		return fmt.Errorf("delete_spawn_loot query not prepared")
	}

	_, err := stmt.Exec(spawnID)
	if err != nil {
		return fmt.Errorf("failed to delete spawn loot: %v", err)
	}

	// Remove from in-memory cache
	ldb.mutex.Lock()
	delete(ldb.spawnLoot, spawnID)
	ldb.mutex.Unlock()

	return nil
}

// GetLootStatistics returns database statistics
func (ldb *LootDatabase) GetLootStatistics() (map[string]interface{}, error) {
	stats := make(map[string]interface{})

	// Count loot tables
	if stmt := ldb.queries["count_loot_tables"]; stmt != nil {
		var count int
		if err := stmt.QueryRow().Scan(&count); err == nil {
			stats["loot_tables"] = count
		}
	}

	// Count loot drops
	if stmt := ldb.queries["count_loot_drops"]; stmt != nil {
		var count int
		if err := stmt.QueryRow().Scan(&count); err == nil {
			stats["loot_drops"] = count
		}
	}

	// Count spawn loot assignments
	if stmt := ldb.queries["count_spawn_loot"]; stmt != nil {
		var count int
		if err := stmt.QueryRow().Scan(&count); err == nil {
			stats["spawn_loot_assignments"] = count
		}
	}

	// In-memory statistics
	ldb.mutex.RLock()
	stats["cached_loot_tables"] = len(ldb.lootTables)
	stats["cached_spawn_assignments"] = len(ldb.spawnLoot)
	stats["cached_global_loot"] = len(ldb.globalLoot)
	ldb.mutex.RUnlock()

	stats["loaded_at"] = time.Now().Format(time.RFC3339)

	return stats, nil
}

// ReloadLootData reloads all loot data from the database
func (ldb *LootDatabase) ReloadLootData() error {
	log.Printf("%s Reloading loot data from database...", LogPrefixDatabase)
	
	return ldb.LoadAllLootData()
}

// Close closes all prepared statements
func (ldb *LootDatabase) Close() error {
	for name, stmt := range ldb.queries {
		if err := stmt.Close(); err != nil {
			log.Printf("%s Error closing statement %s: %v", LogPrefixDatabase, name, err)
		}
	}
	return nil
}