package spells

import (
	"fmt"
	"sync"
	"sync/atomic"
)

// SpellScriptTimer represents a timer for spell script execution
type SpellScriptTimer struct {
	// TODO: Add LuaSpell reference when implemented
	// Spell          *LuaSpell // The spell being timed
	SpellID        int32  // Spell ID for identification
	CustomFunction string // Custom function to call
	Time           int32  // Timer duration
	Caster         int32  // Caster entity ID
	Target         int32  // Target entity ID
	DeleteWhenDone bool   // Whether to delete timer when finished
}

// MasterSpellList manages all spells in the game
// This replaces the C++ MasterSpellList functionality
type MasterSpellList struct {
	// Spell storage
	spells       map[int32]*Spell          // Spells by ID
	spellsByName map[string]*Spell         // Spells by name for lookup
	spellsByTier map[int32]map[int8]*Spell // Spells by ID and tier

	// ID management
	maxSpellID int32 // Highest assigned spell ID

	// Thread safety
	mutex sync.RWMutex
}

// NewMasterSpellList creates a new master spell list
func NewMasterSpellList() *MasterSpellList {
	return &MasterSpellList{
		spells:       make(map[int32]*Spell),
		spellsByName: make(map[string]*Spell),
		spellsByTier: make(map[int32]map[int8]*Spell),
		maxSpellID:   0,
	}
}

// AddSpell adds a spell to the master list
func (msl *MasterSpellList) AddSpell(spell *Spell) bool {
	if spell == nil {
		return false
	}

	msl.mutex.Lock()
	defer msl.mutex.Unlock()

	spellID := spell.GetSpellID()

	// Update max spell ID
	if spellID > msl.maxSpellID {
		msl.maxSpellID = spellID
	}

	// Add to main spell map
	msl.spells[spellID] = spell

	// Add to name lookup
	name := spell.GetName()
	if name != "" {
		msl.spellsByName[name] = spell
	}

	// Add to tier lookup
	tier := spell.GetSpellTier()
	if msl.spellsByTier[spellID] == nil {
		msl.spellsByTier[spellID] = make(map[int8]*Spell)
	}
	msl.spellsByTier[spellID][tier] = spell

	return true
}

// GetSpell retrieves a spell by ID
func (msl *MasterSpellList) GetSpell(spellID int32) *Spell {
	msl.mutex.RLock()
	defer msl.mutex.RUnlock()

	return msl.spells[spellID]
}

// GetSpellByName retrieves a spell by name
func (msl *MasterSpellList) GetSpellByName(name string) *Spell {
	msl.mutex.RLock()
	defer msl.mutex.RUnlock()

	return msl.spellsByName[name]
}

// GetSpellByIDAndTier retrieves a specific tier of a spell
func (msl *MasterSpellList) GetSpellByIDAndTier(spellID int32, tier int8) *Spell {
	msl.mutex.RLock()
	defer msl.mutex.RUnlock()

	if tierMap, exists := msl.spellsByTier[spellID]; exists {
		return tierMap[tier]
	}

	return nil
}

// RemoveSpell removes a spell from the master list
func (msl *MasterSpellList) RemoveSpell(spellID int32) bool {
	msl.mutex.Lock()
	defer msl.mutex.Unlock()

	spell, exists := msl.spells[spellID]
	if !exists {
		return false
	}

	// Remove from main map
	delete(msl.spells, spellID)

	// Remove from name lookup
	name := spell.GetName()
	if name != "" {
		delete(msl.spellsByName, name)
	}

	// Remove from tier lookup
	delete(msl.spellsByTier, spellID)

	return true
}

// GetNewMaxSpellID returns the next available spell ID
func (msl *MasterSpellList) GetNewMaxSpellID() int32 {
	return atomic.AddInt32(&msl.maxSpellID, 1)
}

// GetSpellCount returns the total number of spells
func (msl *MasterSpellList) GetSpellCount() int {
	msl.mutex.RLock()
	defer msl.mutex.RUnlock()

	return len(msl.spells)
}

// GetAllSpells returns a copy of all spells (expensive operation)
func (msl *MasterSpellList) GetAllSpells() []*Spell {
	msl.mutex.RLock()
	defer msl.mutex.RUnlock()

	spells := make([]*Spell, 0, len(msl.spells))
	for _, spell := range msl.spells {
		spells = append(spells, spell)
	}

	return spells
}

// GetSpellsByType returns all spells of a specific type
func (msl *MasterSpellList) GetSpellsByType(spellType int16) []*Spell {
	msl.mutex.RLock()
	defer msl.mutex.RUnlock()

	spells := make([]*Spell, 0)
	for _, spell := range msl.spells {
		if spell.GetSpellData().Type == spellType {
			spells = append(spells, spell)
		}
	}

	return spells
}

// GetSpellsByBookType returns all spells of a specific book type
func (msl *MasterSpellList) GetSpellsByBookType(bookType int32) []*Spell {
	msl.mutex.RLock()
	defer msl.mutex.RUnlock()

	spells := make([]*Spell, 0)
	for _, spell := range msl.spells {
		if spell.GetSpellData().SpellBookType == bookType {
			spells = append(spells, spell)
		}
	}

	return spells
}

// Global spell list instance
var masterSpellList *MasterSpellList
var initSpellListOnce sync.Once

// GetMasterSpellList returns the global master spell list (singleton)
func GetMasterSpellList() *MasterSpellList {
	initSpellListOnce.Do(func() {
		masterSpellList = NewMasterSpellList()
	})
	return masterSpellList
}

// SpellCasting represents an active spell casting attempt
type SpellCasting struct {
	Spell         *Spell // The spell being cast
	Caster        int32  // Caster entity ID
	Target        int32  // Target entity ID
	CastTime      int32  // Total cast time
	TimeRemaining int32  // Time remaining in cast
	Interrupted   bool   // Whether casting was interrupted
	// TODO: Add Entity references when implemented
	// CasterEntity  *Entity
	// TargetEntity  *Entity
}

// SpellBook represents a character's spell book
type SpellBook struct {
	// Spell storage organized by type
	spells       map[int32]*Spell   // All known spells by ID
	spellsByType map[int32][]*Spell // Spells organized by book type

	// Spell bar/hotbar assignments
	spellBars map[int8]map[int8]*Spell // [bar][slot] = spell

	// Thread safety
	mutex sync.RWMutex
}

// NewSpellBook creates a new spell book
func NewSpellBook() *SpellBook {
	return &SpellBook{
		spells:       make(map[int32]*Spell),
		spellsByType: make(map[int32][]*Spell),
		spellBars:    make(map[int8]map[int8]*Spell),
	}
}

// AddSpell adds a spell to the spell book
func (sb *SpellBook) AddSpell(spell *Spell) bool {
	if spell == nil {
		return false
	}

	sb.mutex.Lock()
	defer sb.mutex.Unlock()

	spellID := spell.GetSpellID()

	// Check if spell already exists
	if _, exists := sb.spells[spellID]; exists {
		return false
	}

	// Add to main collection
	sb.spells[spellID] = spell

	// Add to type collection
	bookType := spell.GetSpellData().SpellBookType
	if sb.spellsByType[bookType] == nil {
		sb.spellsByType[bookType] = make([]*Spell, 0)
	}
	sb.spellsByType[bookType] = append(sb.spellsByType[bookType], spell)

	return true
}

// RemoveSpell removes a spell from the spell book
func (sb *SpellBook) RemoveSpell(spellID int32) bool {
	sb.mutex.Lock()
	defer sb.mutex.Unlock()

	spell, exists := sb.spells[spellID]
	if !exists {
		return false
	}

	// Remove from main collection
	delete(sb.spells, spellID)

	// Remove from type collection
	bookType := spell.GetSpellData().SpellBookType
	if spells, exists := sb.spellsByType[bookType]; exists {
		for i, s := range spells {
			if s.GetSpellID() == spellID {
				sb.spellsByType[bookType] = append(spells[:i], spells[i+1:]...)
				break
			}
		}
	}

	// Remove from spell bars
	for barID, bar := range sb.spellBars {
		for slot, s := range bar {
			if s != nil && s.GetSpellID() == spellID {
				delete(sb.spellBars[barID], slot)
			}
		}
	}

	return true
}

// GetSpell retrieves a spell from the spell book
func (sb *SpellBook) GetSpell(spellID int32) *Spell {
	sb.mutex.RLock()
	defer sb.mutex.RUnlock()

	return sb.spells[spellID]
}

// HasSpell checks if the spell book contains a specific spell
func (sb *SpellBook) HasSpell(spellID int32) bool {
	sb.mutex.RLock()
	defer sb.mutex.RUnlock()

	_, exists := sb.spells[spellID]
	return exists
}

// GetSpellsByType returns all spells of a specific book type
func (sb *SpellBook) GetSpellsByType(bookType int32) []*Spell {
	sb.mutex.RLock()
	defer sb.mutex.RUnlock()

	if spells, exists := sb.spellsByType[bookType]; exists {
		// Return a copy to prevent external modification
		result := make([]*Spell, len(spells))
		copy(result, spells)
		return result
	}

	return make([]*Spell, 0)
}

// SetSpellBarSlot assigns a spell to a specific bar and slot
func (sb *SpellBook) SetSpellBarSlot(barID, slot int8, spell *Spell) bool {
	sb.mutex.Lock()
	defer sb.mutex.Unlock()

	// Ensure the spell is in the spell book
	if spell != nil {
		if _, exists := sb.spells[spell.GetSpellID()]; !exists {
			return false
		}
	}

	// Initialize bar if needed
	if sb.spellBars[barID] == nil {
		sb.spellBars[barID] = make(map[int8]*Spell)
	}

	// Set the slot
	if spell == nil {
		delete(sb.spellBars[barID], slot)
	} else {
		sb.spellBars[barID][slot] = spell
	}

	return true
}

// GetSpellBarSlot retrieves the spell at a specific bar and slot
func (sb *SpellBook) GetSpellBarSlot(barID, slot int8) *Spell {
	sb.mutex.RLock()
	defer sb.mutex.RUnlock()

	if bar, exists := sb.spellBars[barID]; exists {
		return bar[slot]
	}

	return nil
}

// GetSpellCount returns the total number of spells in the book
func (sb *SpellBook) GetSpellCount() int {
	sb.mutex.RLock()
	defer sb.mutex.RUnlock()

	return len(sb.spells)
}

// String returns a string representation of the spell book
func (sb *SpellBook) String() string {
	return fmt.Sprintf("SpellBook[Spells=%d, Types=%d]",
		sb.GetSpellCount(), len(sb.spellsByType))
}

// SpellManager manages the master spell list, player spell books, and spell processing
type SpellManager struct {
	masterList      *MasterSpellList      // Global spell definitions
	spellBooks      map[int32]*SpellBook  // Player spell books by character ID
	spellProcess    *SpellProcess         // Spell processing system
	targeting       *SpellTargeting       // Spell targeting system
	resourceChecker *SpellResourceChecker // Resource checking system
	mutex           sync.RWMutex          // Thread safety
}

// NewSpellManager creates a new spell manager
func NewSpellManager() *SpellManager {
	return &SpellManager{
		masterList:      NewMasterSpellList(),
		spellBooks:      make(map[int32]*SpellBook),
		spellProcess:    NewSpellProcess(),
		targeting:       NewSpellTargeting(),
		resourceChecker: NewSpellResourceChecker(),
	}
}

// GetMasterList returns the master spell list
func (sm *SpellManager) GetMasterList() *MasterSpellList {
	return sm.masterList
}

// GetSpellProcess returns the spell processing system
func (sm *SpellManager) GetSpellProcess() *SpellProcess {
	return sm.spellProcess
}

// GetTargeting returns the spell targeting system
func (sm *SpellManager) GetTargeting() *SpellTargeting {
	return sm.targeting
}

// GetResourceChecker returns the resource checking system
func (sm *SpellManager) GetResourceChecker() *SpellResourceChecker {
	return sm.resourceChecker
}

// GetSpellBook retrieves or creates a spell book for a character
func (sm *SpellManager) GetSpellBook(characterID int32) *SpellBook {
	sm.mutex.Lock()
	defer sm.mutex.Unlock()

	if book, exists := sm.spellBooks[characterID]; exists {
		return book
	}

	// Create new spell book
	book := NewSpellBook()
	sm.spellBooks[characterID] = book
	return book
}

// RemoveSpellBook removes a character's spell book (when they log out)
func (sm *SpellManager) RemoveSpellBook(characterID int32) {
	sm.mutex.Lock()
	defer sm.mutex.Unlock()

	delete(sm.spellBooks, characterID)
}

// ProcessSpells processes all active spells (should be called regularly)
func (sm *SpellManager) ProcessSpells() {
	sm.spellProcess.Process()
}

// CastSpell attempts to cast a spell for a character
func (sm *SpellManager) CastSpell(casterID, targetID, spellID int32) error {
	// Get the spell from master list
	spell := sm.masterList.GetSpell(spellID)
	if spell == nil {
		return fmt.Errorf("spell %d not found", spellID)
	}

	// Create LuaSpell instance
	luaSpell := NewLuaSpell(spell, casterID)
	luaSpell.InitialTarget = targetID

	// Check resources
	results := sm.resourceChecker.CheckAllResources(luaSpell, 0, 0)
	for _, result := range results {
		if !result.HasSufficient {
			return fmt.Errorf("insufficient resources: %s", result.ErrorMessage)
		}
	}

	// Get targets
	targetResult := sm.targeting.GetSpellTargets(luaSpell, nil)
	if targetResult.ErrorCode != 0 {
		return fmt.Errorf("targeting failed: %s", targetResult.ErrorMessage)
	}

	if len(targetResult.ValidTargets) == 0 {
		return fmt.Errorf("no valid targets found")
	}

	// TODO: Add spell to cast queue or process immediately
	// This would integrate with the entity system to actually cast the spell

	return nil
}

// InterruptSpell interrupts a spell being cast by an entity
func (sm *SpellManager) InterruptSpell(entityID, spellID int32, errorCode int16, cancel, fromMovement bool) {
	sm.spellProcess.Interrupt(entityID, spellID, errorCode, cancel, fromMovement)
}

// AddSpellToQueue adds a spell to a player's casting queue
func (sm *SpellManager) AddSpellToQueue(spellID, casterID, targetID int32, priority int32) {
	sm.spellProcess.AddSpellToQueue(spellID, casterID, targetID, priority)
}

// RemoveSpellFromQueue removes a spell from a player's casting queue
func (sm *SpellManager) RemoveSpellFromQueue(spellID, casterID int32) bool {
	return sm.spellProcess.RemoveSpellFromQueue(spellID, casterID)
}

// IsSpellReady checks if a spell is ready to cast (not on cooldown)
func (sm *SpellManager) IsSpellReady(spellID, casterID int32) bool {
	return sm.spellProcess.IsReady(spellID, casterID)
}

// GetSpellRecastTime returns remaining recast time for a spell
func (sm *SpellManager) GetSpellRecastTime(spellID, casterID int32) int32 {
	remaining := sm.spellProcess.GetRecastTimeRemaining(spellID, casterID)
	return int32(remaining.Milliseconds())
}

// CanCastSpell performs comprehensive checks to determine if a spell can be cast
func (sm *SpellManager) CanCastSpell(casterID, targetID, spellID int32) (bool, string) {
	// Check if spell exists
	spell := sm.masterList.GetSpell(spellID)
	if spell == nil {
		return false, "Spell not found"
	}

	// Check if spell is ready (not on cooldown)
	if !sm.spellProcess.IsReady(spellID, casterID) {
		remaining := sm.spellProcess.GetRecastTimeRemaining(spellID, casterID)
		return false, fmt.Sprintf("Spell on cooldown for %d seconds", int(remaining.Seconds()))
	}

	// Create temporary LuaSpell for resource checks
	luaSpell := NewLuaSpell(spell, casterID)
	luaSpell.InitialTarget = targetID

	// Check resources
	results := sm.resourceChecker.CheckAllResources(luaSpell, 0, 0)
	for _, result := range results {
		if !result.HasSufficient {
			return false, result.ErrorMessage
		}
	}

	// Check targeting
	targetResult := sm.targeting.GetSpellTargets(luaSpell, nil)
	if targetResult.ErrorCode != 0 {
		return false, targetResult.ErrorMessage
	}

	if len(targetResult.ValidTargets) == 0 {
		return false, "No valid targets"
	}

	return true, ""
}

// GetSpellInfo returns comprehensive information about a spell
func (sm *SpellManager) GetSpellInfo(spellID int32) map[string]interface{} {
	spell := sm.masterList.GetSpell(spellID)
	if spell == nil {
		return nil
	}

	info := make(map[string]interface{})

	// Basic spell info
	info["id"] = spell.GetSpellID()
	info["name"] = spell.GetName()
	info["description"] = spell.GetDescription()
	info["tier"] = spell.GetSpellTier()
	info["type"] = spell.GetSpellData().SpellBookType

	// Resource requirements
	info["resources"] = sm.resourceChecker.GetResourceSummary(spell)

	// Targeting info
	info["targeting"] = sm.targeting.GetTargetingInfo(spell)

	// Classification
	info["is_buff"] = spell.IsBuffSpell()
	info["is_debuff"] = !spell.IsBuffSpell() && spell.IsControlSpell()
	info["is_heal"] = spell.IsHealSpell()
	info["is_damage"] = spell.IsDamageSpell()
	info["is_offensive"] = spell.IsOffenseSpell()

	return info
}

// GetActiveSpellCount returns the number of active spells being processed
func (sm *SpellManager) GetActiveSpellCount() int {
	return sm.spellProcess.GetActiveSpellCount()
}

// Shutdown gracefully shuts down the spell manager
func (sm *SpellManager) Shutdown() {
	sm.spellProcess.RemoveAllSpells(false)
}
