package loot

import (
	"fmt"
	"log"
	"math/rand"
	"sync"
	"time"

	"eq2emu/internal/items"
)

// LootManager handles all loot generation and management
type LootManager struct {
	database      *LootDatabase
	itemMasterList items.MasterItemListService
	statistics    *LootStatistics
	treasureChests map[int32]*TreasureChest // chest_id -> TreasureChest
	chestIDCounter int32
	random        *rand.Rand
	mutex         sync.RWMutex
}

// NewLootManager creates a new loot manager
func NewLootManager(database *LootDatabase, itemMasterList items.MasterItemListService) *LootManager {
	return &LootManager{
		database:       database,
		itemMasterList: itemMasterList,
		statistics:     NewLootStatistics(),
		treasureChests: make(map[int32]*TreasureChest),
		chestIDCounter: 1,
		random:         rand.New(rand.NewSource(time.Now().UnixNano())),
	}
}

// GenerateLoot generates loot for a spawn based on its loot table assignments
func (lm *LootManager) GenerateLoot(spawnID int32, context *LootContext) (*LootResult, error) {
	log.Printf("%s Generating loot for spawn %d", LogPrefixGeneration, spawnID)
	
	result := &LootResult{
		Items: make([]*items.Item, 0),
		Coins: 0,
	}

	// Get loot tables for this spawn
	tableIDs := lm.database.GetSpawnLootTables(spawnID)
	
	// Also check for global loot tables
	globalLoot := lm.database.GetGlobalLootTables(context.PlayerLevel, context.PlayerRace, context.ZoneID)
	for _, global := range globalLoot {
		tableIDs = append(tableIDs, global.TableID)
	}

	if len(tableIDs) == 0 {
		log.Printf("%s No loot tables found for spawn %d", LogPrefixGeneration, spawnID)
		return result, nil
	}

	// Process each loot table
	for _, tableID := range tableIDs {
		if err := lm.processLootTable(tableID, context, result); err != nil {
			log.Printf("%s Error processing loot table %d: %v", LogPrefixGeneration, tableID, err)
			continue
		}
	}

	// Record statistics
	if len(tableIDs) > 0 {
		lm.statistics.RecordLoot(tableIDs[0], result) // Use first table for stats
	}

	log.Printf("%s Generated %d items and %d coins for spawn %d", 
		LogPrefixGeneration, len(result.Items), result.Coins, spawnID)
	
	return result, nil
}

// processLootTable processes a single loot table and adds results to the loot result
func (lm *LootManager) processLootTable(tableID int32, context *LootContext, result *LootResult) error {
	table := lm.database.GetLootTable(tableID)
	if table == nil {
		return fmt.Errorf("loot table %d not found", tableID)
	}

	lm.mutex.Lock()
	defer lm.mutex.Unlock()

	// Check if loot should drop at all
	if !lm.rollProbability(table.LootDropProbability) {
		log.Printf("%s Loot table %d failed drop probability check", LogPrefixGeneration, tableID)
		return nil
	}

	// Generate coins if probability succeeds
	if lm.rollProbability(table.CoinProbability) {
		coins := lm.generateCoins(table.MinCoin, table.MaxCoin)
		result.AddCoins(coins)
		log.Printf("%s Generated %d coins from table %d", LogPrefixGeneration, coins, tableID)
	}

	// Generate items
	itemsGenerated := 0
	maxItems := int(table.MaxLootItems)
	if maxItems <= 0 {
		maxItems = DefaultMaxLootItems
	}

	// Process each loot drop
	for _, drop := range table.Drops {
		// Check if we've hit the max item limit
		if itemsGenerated >= maxItems {
			break
		}

		// Check quest requirement
		if drop.NoDropQuestCompletedID > 0 {
			if !context.CompletedQuests[drop.NoDropQuestCompletedID] {
				continue // Player hasn't completed required quest
			}
		}

		// Roll probability for this drop
		if !lm.rollProbability(drop.Probability) {
			continue
		}

		// Get item template
		itemTemplate := lm.itemMasterList.GetItem(drop.ItemID)
		if itemTemplate == nil {
			log.Printf("%s Item template %d not found for loot drop", LogPrefixGeneration, drop.ItemID)
			continue
		}

		// Create item instance
		item := items.NewItemFromTemplate(itemTemplate)
		
		// Set charges if specified
		if drop.ItemCharges > 0 {
			item.Details.Count = drop.ItemCharges
		}

		// Mark as equipped if specified
		if drop.EquipItem {
			// This would be handled by the caller when distributing loot
			// For now, we just note it in the item
		}

		result.AddItem(item)
		itemsGenerated++
		
		log.Printf("%s Generated item %d (%s) from table %d", 
			LogPrefixGeneration, drop.ItemID, item.Name, tableID)
	}

	return nil
}

// rollProbability rolls a probability check (0-100%)
func (lm *LootManager) rollProbability(probability float32) bool {
	if probability <= 0 {
		return false
	}
	if probability >= 100.0 {
		return true
	}
	
	roll := lm.random.Float32() * 100.0
	return roll <= probability
}

// generateCoins generates a random coin amount between min and max
func (lm *LootManager) generateCoins(minCoin, maxCoin int32) int32 {
	if minCoin >= maxCoin {
		return minCoin
	}
	
	return minCoin + lm.random.Int31n(maxCoin-minCoin+1)
}

// CreateTreasureChest creates a treasure chest for loot
func (lm *LootManager) CreateTreasureChest(spawnID int32, zoneID int32, x, y, z, heading float32, 
	lootResult *LootResult, lootRights []uint32) (*TreasureChest, error) {
	
	lm.mutex.Lock()
	defer lm.mutex.Unlock()

	// Generate unique chest ID
	chestID := lm.chestIDCounter
	lm.chestIDCounter++

	// Determine chest appearance based on highest item tier
	highestTier := lm.getHighestItemTier(lootResult.GetItems())
	appearance := GetChestAppearance(highestTier)

	chest := &TreasureChest{
		ID:           chestID,
		SpawnID:      spawnID,
		ZoneID:       zoneID,
		X:            x,
		Y:            y,
		Z:            z,
		Heading:      heading,
		AppearanceID: appearance.AppearanceID,
		LootResult:   lootResult,
		Created:      time.Now(),
		LootRights:   make([]uint32, len(lootRights)),
		IsDisarmable: false, // TODO: Implement trap system
		IsLocked:     false, // TODO: Implement lock system
	}

	// Copy loot rights
	copy(chest.LootRights, lootRights)

	// Store chest
	lm.treasureChests[chestID] = chest
	
	// Record statistics
	lm.statistics.RecordChest()

	log.Printf("%s Created treasure chest %d (%s) at (%.2f, %.2f, %.2f) with %d items and %d coins", 
		LogPrefixChest, chestID, appearance.Name, x, y, z, 
		len(lootResult.GetItems()), lootResult.GetCoins())

	return chest, nil
}

// getHighestItemTier finds the highest tier among items
func (lm *LootManager) getHighestItemTier(items []*items.Item) int8 {
	var highest int8 = LootTierCommon
	
	for _, item := range items {
		if item.Details.Tier > highest {
			highest = item.Details.Tier
		}
	}
	
	return highest
}

// GetTreasureChest returns a treasure chest by ID
func (lm *LootManager) GetTreasureChest(chestID int32) *TreasureChest {
	lm.mutex.RLock()
	defer lm.mutex.RUnlock()
	
	return lm.treasureChests[chestID]
}

// RemoveTreasureChest removes a treasure chest
func (lm *LootManager) RemoveTreasureChest(chestID int32) {
	lm.mutex.Lock()
	defer lm.mutex.Unlock()
	
	delete(lm.treasureChests, chestID)
	log.Printf("%s Removed treasure chest %d", LogPrefixChest, chestID)
}

// LootChestItem removes a specific item from a chest
func (lm *LootManager) LootChestItem(chestID int32, playerID uint32, itemUniqueID int64) (*items.Item, error) {
	lm.mutex.Lock()
	defer lm.mutex.Unlock()

	chest := lm.treasureChests[chestID]
	if chest == nil {
		return nil, fmt.Errorf("treasure chest %d not found", chestID)
	}

	// Check loot rights
	if !chest.HasLootRights(playerID) {
		return nil, fmt.Errorf("player %d has no loot rights for chest %d", playerID, chestID)
	}

	// Find and remove the item
	lootItems := chest.LootResult.GetItems()
	for i, item := range lootItems {
		if item.Details.UniqueID == itemUniqueID {
			// Remove item from slice
			chest.LootResult.mutex.Lock()
			chest.LootResult.Items = append(chest.LootResult.Items[:i], chest.LootResult.Items[i+1:]...)
			chest.LootResult.mutex.Unlock()
			
			log.Printf("%s Player %d looted item %d (%s) from chest %d", 
				LogPrefixChest, playerID, item.Details.ItemID, item.Name, chestID)
			
			return item, nil
		}
	}

	return nil, fmt.Errorf("item %d not found in chest %d", itemUniqueID, chestID)
}

// LootChestCoins removes coins from a chest
func (lm *LootManager) LootChestCoins(chestID int32, playerID uint32) (int32, error) {
	lm.mutex.Lock()
	defer lm.mutex.Unlock()

	chest := lm.treasureChests[chestID]
	if chest == nil {
		return 0, fmt.Errorf("treasure chest %d not found", chestID)
	}

	// Check loot rights
	if !chest.HasLootRights(playerID) {
		return 0, fmt.Errorf("player %d has no loot rights for chest %d", playerID, chestID)
	}

	coins := chest.LootResult.GetCoins()
	if coins <= 0 {
		return 0, nil
	}

	// Remove coins from chest
	chest.LootResult.mutex.Lock()
	chest.LootResult.Coins = 0
	chest.LootResult.mutex.Unlock()

	log.Printf("%s Player %d looted %d coins from chest %d", 
		LogPrefixChest, playerID, coins, chestID)

	return coins, nil
}

// LootChestAll removes all items and coins from a chest
func (lm *LootManager) LootChestAll(chestID int32, playerID uint32) (*LootResult, error) {
	lm.mutex.Lock()
	defer lm.mutex.Unlock()

	chest := lm.treasureChests[chestID]
	if chest == nil {
		return nil, fmt.Errorf("treasure chest %d not found", chestID)
	}

	// Check loot rights
	if !chest.HasLootRights(playerID) {
		return nil, fmt.Errorf("player %d has no loot rights for chest %d", playerID, chestID)
	}

	// Get all loot
	result := &LootResult{
		Items: chest.LootResult.GetItems(),
		Coins: chest.LootResult.GetCoins(),
	}

	// Clear chest loot
	chest.LootResult.mutex.Lock()
	chest.LootResult.Items = make([]*items.Item, 0)
	chest.LootResult.Coins = 0
	chest.LootResult.mutex.Unlock()

	log.Printf("%s Player %d looted all (%d items, %d coins) from chest %d", 
		LogPrefixChest, playerID, len(result.Items), result.Coins, chestID)

	return result, nil
}

// IsChestEmpty checks if a chest has no loot
func (lm *LootManager) IsChestEmpty(chestID int32) bool {
	lm.mutex.RLock()
	defer lm.mutex.RUnlock()

	chest := lm.treasureChests[chestID]
	if chest == nil {
		return true
	}

	return chest.LootResult.IsEmpty()
}

// CleanupExpiredChests removes chests that have been around too long
func (lm *LootManager) CleanupExpiredChests() {
	lm.mutex.Lock()
	defer lm.mutex.Unlock()

	now := time.Now()
	var expired []int32

	for chestID, chest := range lm.treasureChests {
		age := now.Sub(chest.Created).Seconds()
		
		// Remove empty chests after ChestDespawnTime
		if chest.LootResult.IsEmpty() && age > ChestDespawnTime {
			expired = append(expired, chestID)
		}
		
		// Force remove all chests after ChestCleanupTime
		if age > ChestCleanupTime {
			expired = append(expired, chestID)
		}
	}

	for _, chestID := range expired {
		delete(lm.treasureChests, chestID)
		log.Printf("%s Cleaned up expired chest %d", LogPrefixChest, chestID)
	}

	if len(expired) > 0 {
		log.Printf("%s Cleaned up %d expired chests", LogPrefixChest, len(expired))
	}
}

// GetZoneChests returns all chests in a specific zone
func (lm *LootManager) GetZoneChests(zoneID int32) []*TreasureChest {
	lm.mutex.RLock()
	defer lm.mutex.RUnlock()

	var chests []*TreasureChest
	for _, chest := range lm.treasureChests {
		if chest.ZoneID == zoneID {
			chests = append(chests, chest)
		}
	}

	return chests
}

// GetPlayerChests returns all chests a player has loot rights to
func (lm *LootManager) GetPlayerChests(playerID uint32) []*TreasureChest {
	lm.mutex.RLock()
	defer lm.mutex.RUnlock()

	var chests []*TreasureChest
	for _, chest := range lm.treasureChests {
		if chest.HasLootRights(playerID) {
			chests = append(chests, chest)
		}
	}

	return chests
}

// GetStatistics returns loot generation statistics  
func (lm *LootManager) GetStatistics() LootStatistics {
	return lm.statistics.GetStatistics()
}

// ReloadLootData reloads loot data from the database
func (lm *LootManager) ReloadLootData() error {
	log.Printf("%s Reloading loot data...", LogPrefixLoot)
	return lm.database.ReloadLootData()
}

// AddLootTable adds a new loot table
func (lm *LootManager) AddLootTable(table *LootTable) error {
	log.Printf("%s Adding loot table %d (%s)", LogPrefixLoot, table.ID, table.Name)
	return lm.database.AddLootTable(table)
}

// UpdateLootTable updates an existing loot table
func (lm *LootManager) UpdateLootTable(table *LootTable) error {
	log.Printf("%s Updating loot table %d (%s)", LogPrefixLoot, table.ID, table.Name)
	return lm.database.UpdateLootTable(table)
}

// DeleteLootTable removes a loot table
func (lm *LootManager) DeleteLootTable(tableID int32) error {
	log.Printf("%s Deleting loot table %d", LogPrefixLoot, tableID)
	return lm.database.DeleteLootTable(tableID)
}

// AssignSpawnLoot assigns a loot table to a spawn
func (lm *LootManager) AssignSpawnLoot(spawnID, tableID int32) error {
	log.Printf("%s Assigning loot table %d to spawn %d", LogPrefixLoot, tableID, spawnID)
	return lm.database.AddSpawnLoot(spawnID, tableID)
}

// RemoveSpawnLoot removes loot table assignments from a spawn
func (lm *LootManager) RemoveSpawnLoot(spawnID int32) error {
	log.Printf("%s Removing loot assignments from spawn %d", LogPrefixLoot, spawnID)
	return lm.database.DeleteSpawnLoot(spawnID)
}

// StartCleanupTimer starts a background timer to clean up expired chests
func (lm *LootManager) StartCleanupTimer() {
	go func() {
		ticker := time.NewTicker(5 * time.Minute) // Clean up every 5 minutes
		defer ticker.Stop()

		for range ticker.C {
			lm.CleanupExpiredChests()
		}
	}()
	
	log.Printf("%s Started chest cleanup timer", LogPrefixLoot)
}