package spells

import (
	"fmt"
	"sync"
)

// Spell represents a complete spell with data, levels, effects, and LUA data
// This is the main spell class converted from C++ Spell class
type Spell struct {
	// Core spell data
	data *SpellData

	// Spell progression and requirements
	levels  []*LevelArray         // Level requirements by class
	effects []*SpellDisplayEffect // Display effects for tooltips
	luaData []*LUAData            // LUA script data

	// Computed properties (cached for performance)
	healSpell    bool // Cached: is this a healing spell
	buffSpell    bool // Cached: is this a buff spell
	damageSpell  bool // Cached: is this a damage spell
	controlSpell bool // Cached: is this a control spell
	offenseSpell bool // Cached: is this an offensive spell
	copiedSpell  bool // Whether this is a copied/derived spell

	// Runtime state
	stayLocked bool // Whether spell should stay locked

	// Thread safety
	mutex sync.RWMutex
}

// NewSpell creates a new spell with default spell data
func NewSpell() *Spell {
	return &Spell{
		data:         NewSpellData(),
		levels:       make([]*LevelArray, 0),
		effects:      make([]*SpellDisplayEffect, 0),
		luaData:      make([]*LUAData, 0),
		healSpell:    false,
		buffSpell:    false,
		damageSpell:  false,
		controlSpell: false,
		offenseSpell: false,
		copiedSpell:  false,
		stayLocked:   false,
	}
}

// NewSpellFromData creates a new spell with existing spell data
func NewSpellFromData(spellData *SpellData) *Spell {
	s := NewSpell()
	s.data = spellData
	s.computeSpellProperties()
	return s
}

// NewSpellCopy creates a copy of an existing spell (for spell upgrades/variants)
func NewSpellCopy(hostSpell *Spell, uniqueSpell bool) *Spell {
	if hostSpell == nil {
		return NewSpell()
	}

	hostSpell.mutex.RLock()
	defer hostSpell.mutex.RUnlock()

	s := &Spell{
		data:         hostSpell.data.Clone(),
		levels:       make([]*LevelArray, 0),
		effects:      make([]*SpellDisplayEffect, 0),
		luaData:      make([]*LUAData, 0),
		healSpell:    hostSpell.healSpell,
		buffSpell:    hostSpell.buffSpell,
		damageSpell:  hostSpell.damageSpell,
		controlSpell: hostSpell.controlSpell,
		offenseSpell: hostSpell.offenseSpell,
		copiedSpell:  true,
		stayLocked:   hostSpell.stayLocked,
	}

	// Copy levels
	for _, level := range hostSpell.levels {
		newLevel := &LevelArray{
			AdventureClass:    level.AdventureClass,
			TradeskillClass:   level.TradeskillClass,
			SpellLevel:        level.SpellLevel,
			ClassicSpellLevel: level.ClassicSpellLevel,
		}
		s.levels = append(s.levels, newLevel)
	}

	// Copy effects
	for _, effect := range hostSpell.effects {
		newEffect := &SpellDisplayEffect{
			Percentage:  effect.Percentage,
			Subbullet:   effect.Subbullet,
			Description: effect.Description,
			NeedsDBSave: effect.NeedsDBSave,
		}
		s.effects = append(s.effects, newEffect)
	}

	// Copy LUA data
	for _, lua := range hostSpell.luaData {
		newLua := &LUAData{
			Type:         lua.Type,
			IntValue:     lua.IntValue,
			BoolValue:    lua.BoolValue,
			FloatValue:   lua.FloatValue,
			StringValue:  lua.StringValue,
			StringValue2: lua.StringValue2,
			IntValue2:    lua.IntValue2,
			FloatValue2:  lua.FloatValue2,
			StringHelper: lua.StringHelper,
			NeedsDBSave:  lua.NeedsDBSave,
		}
		s.luaData = append(s.luaData, newLua)
	}

	// If unique spell, generate new ID
	if uniqueSpell {
		// TODO: Generate unique spell ID when spell ID management is implemented
		// s.data.SetID(GenerateUniqueSpellID())
	}

	return s
}

// GetSpellData returns the core spell data (thread-safe)
func (s *Spell) GetSpellData() *SpellData {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.data
}

// GetSpellID returns the spell's unique ID
func (s *Spell) GetSpellID() int32 {
	return s.data.GetID()
}

// GetName returns the spell's name
func (s *Spell) GetName() string {
	return s.data.GetName()
}

// GetDescription returns the spell's description
func (s *Spell) GetDescription() string {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.data.Description
}

// GetSpellTier returns the spell's tier
func (s *Spell) GetSpellTier() int8 {
	return s.data.GetTier()
}

// GetSpellDuration returns the spell's duration
func (s *Spell) GetSpellDuration() int32 {
	return s.data.GetDuration()
}

// GetSpellIcon returns the spell's icon ID
func (s *Spell) GetSpellIcon() int16 {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.data.Icon
}

// GetSpellIconBackdrop returns the spell's icon backdrop
func (s *Spell) GetSpellIconBackdrop() int16 {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.data.IconBackdrop
}

// GetSpellIconHeroicOp returns the spell's heroic opportunity icon
func (s *Spell) GetSpellIconHeroicOp() int16 {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.data.IconHeroicOp
}

// AddSpellLevel adds a level requirement for a specific class
func (s *Spell) AddSpellLevel(adventureClass, tradeskillClass int8, level int16, classicLevel float32) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	levelArray := &LevelArray{
		AdventureClass:    adventureClass,
		TradeskillClass:   tradeskillClass,
		SpellLevel:        level,
		ClassicSpellLevel: classicLevel,
	}

	s.levels = append(s.levels, levelArray)
}

// AddSpellEffect adds a display effect to the spell
func (s *Spell) AddSpellEffect(percentage, subbullet int8, description string) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	effect := &SpellDisplayEffect{
		Percentage:  percentage,
		Subbullet:   subbullet,
		Description: description,
		NeedsDBSave: true,
	}

	s.effects = append(s.effects, effect)
}

// AddSpellLuaData adds LUA data to the spell
func (s *Spell) AddSpellLuaData(dataType int8, intValue, intValue2 int32, floatValue, floatValue2 float32, boolValue bool, stringValue, stringValue2, helper string) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	luaData := &LUAData{
		Type:         dataType,
		IntValue:     intValue,
		BoolValue:    boolValue,
		FloatValue:   floatValue,
		StringValue:  stringValue,
		StringValue2: stringValue2,
		IntValue2:    intValue2,
		FloatValue2:  floatValue2,
		StringHelper: helper,
		NeedsDBSave:  true,
	}

	s.luaData = append(s.luaData, luaData)
}

// Convenience methods for adding specific LUA data types

// AddSpellLuaDataInt adds integer LUA data
func (s *Spell) AddSpellLuaDataInt(value, value2 int32, helper string) {
	s.AddSpellLuaData(SpellLUADataTypeInt, value, value2, 0.0, 0.0, false, "", "", helper)
}

// AddSpellLuaDataFloat adds float LUA data
func (s *Spell) AddSpellLuaDataFloat(value, value2 float32, helper string) {
	s.AddSpellLuaData(SpellLUADataTypeFloat, 0, 0, value, value2, false, "", "", helper)
}

// AddSpellLuaDataBool adds boolean LUA data
func (s *Spell) AddSpellLuaDataBool(value bool, helper string) {
	s.AddSpellLuaData(SpellLUADataTypeBool, 0, 0, 0.0, 0.0, value, "", "", helper)
}

// AddSpellLuaDataString adds string LUA data
func (s *Spell) AddSpellLuaDataString(value, value2, helper string) {
	s.AddSpellLuaData(SpellLUADataTypeString, 0, 0, 0.0, 0.0, false, value, value2, helper)
}

// GetSpellLevels returns the spell level requirements
func (s *Spell) GetSpellLevels() []*LevelArray {
	s.mutex.RLock()
	defer s.mutex.RUnlock()

	// Return a copy to prevent external modification
	levels := make([]*LevelArray, len(s.levels))
	copy(levels, s.levels)
	return levels
}

// GetSpellEffects returns the spell display effects
func (s *Spell) GetSpellEffects() []*SpellDisplayEffect {
	s.mutex.RLock()
	defer s.mutex.RUnlock()

	// Return a copy to prevent external modification
	effects := make([]*SpellDisplayEffect, len(s.effects))
	copy(effects, s.effects)
	return effects
}

// GetSpellEffectSafe returns a spell effect safely by index
func (s *Spell) GetSpellEffectSafe(index int) *SpellDisplayEffect {
	s.mutex.RLock()
	defer s.mutex.RUnlock()

	if index < 0 || index >= len(s.effects) {
		return nil
	}
	return s.effects[index]
}

// GetLUAData returns the spell's LUA data
func (s *Spell) GetLUAData() []*LUAData {
	s.mutex.RLock()
	defer s.mutex.RUnlock()

	// Return a copy to prevent external modification
	luaData := make([]*LUAData, len(s.luaData))
	copy(luaData, s.luaData)
	return luaData
}

// Spell classification methods (cached for performance)

// IsHealSpell returns whether this is a healing spell
func (s *Spell) IsHealSpell() bool {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.healSpell
}

// IsBuffSpell returns whether this is a buff spell
func (s *Spell) IsBuffSpell() bool {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.buffSpell
}

// IsDamageSpell returns whether this is a damage spell
func (s *Spell) IsDamageSpell() bool {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.damageSpell
}

// IsControlSpell returns whether this is a control spell
func (s *Spell) IsControlSpell() bool {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.controlSpell
}

// IsOffenseSpell returns whether this is an offensive spell
func (s *Spell) IsOffenseSpell() bool {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.offenseSpell
}

// IsCopiedSpell returns whether this is a copied spell
func (s *Spell) IsCopiedSpell() bool {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.copiedSpell
}

// GetStayLocked returns whether the spell should stay locked
func (s *Spell) GetStayLocked() bool {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.stayLocked
}

// StayLocked sets whether the spell should stay locked
func (s *Spell) StayLocked(val bool) {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	s.stayLocked = val
}

// Cast type checking methods

// CastWhileStunned returns whether spell can be cast while stunned
func (s *Spell) CastWhileStunned() bool {
	return s.data.CanCastWhileStunned()
}

// CastWhileMezzed returns whether spell can be cast while mezzed
func (s *Spell) CastWhileMezzed() bool {
	return s.data.CanCastWhileMezzed()
}

// CastWhileStifled returns whether spell can be cast while stifled
func (s *Spell) CastWhileStifled() bool {
	return s.data.CanCastWhileStifled()
}

// CastWhileFeared returns whether spell can be cast while feared
func (s *Spell) CastWhileFeared() bool {
	return s.data.CanCastWhileFeared()
}

// Requirement checking methods

// GetLevelRequired returns the required level for a specific player
func (s *Spell) GetLevelRequired(playerClass int8, tradeskillClass int8) int16 {
	s.mutex.RLock()
	defer s.mutex.RUnlock()

	for _, level := range s.levels {
		if level.AdventureClass == playerClass || level.TradeskillClass == tradeskillClass {
			return level.SpellLevel
		}
	}

	return 0 // No specific requirement found
}

// GetHPRequired returns HP required to cast (could be modified by entity stats)
func (s *Spell) GetHPRequired() int16 {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.data.HPReq
}

// GetPowerRequired returns power required to cast
func (s *Spell) GetPowerRequired() float32 {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.data.PowerReq
}

// GetSavageryRequired returns savagery required to cast
func (s *Spell) GetSavageryRequired() int16 {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.data.SavageryReq
}

// GetDissonanceRequired returns dissonance required to cast
func (s *Spell) GetDissonanceRequired() int16 {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	return s.data.DissonanceReq
}

// computeSpellProperties analyzes the spell to determine its classification
func (s *Spell) computeSpellProperties() {
	// This would analyze spell effects, target types, etc. to determine spell classification
	// For now, use basic logic based on spell data

	s.buffSpell = s.data.IsBuffSpell()
	s.controlSpell = s.data.IsControlSpell()
	s.offenseSpell = s.data.IsOffenseSpell()
	s.healSpell = s.data.IsHealSpell()
	s.damageSpell = s.data.IsDamageSpell()
}

// String returns a string representation of the spell
func (s *Spell) String() string {
	return fmt.Sprintf("Spell[ID=%d, Name=%s, Tier=%d]",
		s.GetSpellID(), s.GetName(), s.GetSpellTier())
}

// LuaSpell represents an active spell instance with runtime state
// This is converted from the C++ LuaSpell class
type LuaSpell struct {
	// Core identification
	Spell    *Spell // Reference to the spell definition
	CasterID int32  // ID of the entity casting this spell

	// Targeting
	InitialTarget int32   // Original target ID
	Targets       []int32 // Current target IDs

	// Timing and state
	Timer    SpellTimer // Timer for duration/tick tracking
	NumCalls int32      // Number of times spell has ticked
	Restored bool       // Whether this spell was restored from DB
	SlotPos  int16      // Spell book slot position

	// Runtime flags
	Interrupted     bool  // Whether spell was interrupted
	Deleted         bool  // Whether spell is marked for deletion
	HasProc         bool  // Whether spell has proc effects
	CasterCharID    int32 // Character ID of caster (for cross-zone)
	DamageRemaining int32 // Remaining damage for DOT spells
	EffectBitmask   int32 // Bitmask of active effects

	// Spell-specific data
	CustomFunction string  // Custom LUA function name
	ResurrectHP    float32 // HP to restore on resurrect
	ResurrectPower float32 // Power to restore on resurrect

	// Thread safety
	mutex sync.RWMutex
}

// SpellTimer represents timing information for an active spell
type SpellTimer struct {
	StartTime    int64 // When the spell started (milliseconds)
	Duration     int32 // Total duration in milliseconds
	SetAtTrigger int64 // Time when timer was set/triggered
}

// NewLuaSpell creates a new LuaSpell instance
func NewLuaSpell(spell *Spell, casterID int32) *LuaSpell {
	return &LuaSpell{
		Spell:           spell,
		CasterID:        casterID,
		InitialTarget:   0,
		Targets:         make([]int32, 0),
		Timer:           SpellTimer{},
		NumCalls:        0,
		Restored:        false,
		SlotPos:         0,
		Interrupted:     false,
		Deleted:         false,
		HasProc:         false,
		CasterCharID:    0,
		DamageRemaining: 0,
		EffectBitmask:   0,
		CustomFunction:  "",
		ResurrectHP:     0.0,
		ResurrectPower:  0.0,
	}
}

// GetTargets returns a copy of the target list
func (ls *LuaSpell) GetTargets() []int32 {
	ls.mutex.RLock()
	defer ls.mutex.RUnlock()

	targets := make([]int32, len(ls.Targets))
	copy(targets, ls.Targets)
	return targets
}

// AddTarget adds a target to the spell
func (ls *LuaSpell) AddTarget(targetID int32) {
	ls.mutex.Lock()
	defer ls.mutex.Unlock()

	// Check if target already exists
	for _, id := range ls.Targets {
		if id == targetID {
			return
		}
	}

	ls.Targets = append(ls.Targets, targetID)
}

// RemoveTarget removes a target from the spell
func (ls *LuaSpell) RemoveTarget(targetID int32) bool {
	ls.mutex.Lock()
	defer ls.mutex.Unlock()

	for i, id := range ls.Targets {
		if id == targetID {
			ls.Targets = append(ls.Targets[:i], ls.Targets[i+1:]...)
			return true
		}
	}

	return false
}

// HasTarget checks if the spell targets a specific entity
func (ls *LuaSpell) HasTarget(targetID int32) bool {
	ls.mutex.RLock()
	defer ls.mutex.RUnlock()

	for _, id := range ls.Targets {
		if id == targetID {
			return true
		}
	}

	return false
}

// GetTargetCount returns the number of targets
func (ls *LuaSpell) GetTargetCount() int {
	ls.mutex.RLock()
	defer ls.mutex.RUnlock()

	return len(ls.Targets)
}

// ClearTargets removes all targets from the spell
func (ls *LuaSpell) ClearTargets() {
	ls.mutex.Lock()
	defer ls.mutex.Unlock()

	ls.Targets = ls.Targets[:0]
}

// SetCustomFunction sets a custom LUA function for this spell instance
func (ls *LuaSpell) SetCustomFunction(functionName string) {
	ls.mutex.Lock()
	defer ls.mutex.Unlock()

	ls.CustomFunction = functionName
}

// GetCustomFunction returns the custom LUA function name
func (ls *LuaSpell) GetCustomFunction() string {
	ls.mutex.RLock()
	defer ls.mutex.RUnlock()

	return ls.CustomFunction
}

// MarkForDeletion marks the spell for removal
func (ls *LuaSpell) MarkForDeletion() {
	ls.mutex.Lock()
	defer ls.mutex.Unlock()

	ls.Deleted = true
}

// IsDeleted returns whether the spell is marked for deletion
func (ls *LuaSpell) IsDeleted() bool {
	ls.mutex.RLock()
	defer ls.mutex.RUnlock()

	return ls.Deleted
}

// SetInterrupted marks the spell as interrupted
func (ls *LuaSpell) SetInterrupted(interrupted bool) {
	ls.mutex.Lock()
	defer ls.mutex.Unlock()

	ls.Interrupted = interrupted
}

// IsInterrupted returns whether the spell was interrupted
func (ls *LuaSpell) IsInterrupted() bool {
	ls.mutex.RLock()
	defer ls.mutex.RUnlock()

	return ls.Interrupted
}

// SetResurrectValues sets HP and power restoration values for resurrect spells
func (ls *LuaSpell) SetResurrectValues(hp, power float32) {
	ls.mutex.Lock()
	defer ls.mutex.Unlock()

	ls.ResurrectHP = hp
	ls.ResurrectPower = power
}

// GetResurrectValues returns HP and power restoration values
func (ls *LuaSpell) GetResurrectValues() (float32, float32) {
	ls.mutex.RLock()
	defer ls.mutex.RUnlock()

	return ls.ResurrectHP, ls.ResurrectPower
}

// String returns a string representation of the LuaSpell
func (ls *LuaSpell) String() string {
	ls.mutex.RLock()
	defer ls.mutex.RUnlock()

	spellName := "Unknown"
	if ls.Spell != nil {
		spellName = ls.Spell.GetName()
	}

	return fmt.Sprintf("LuaSpell[%s, Caster=%d, Targets=%d]",
		spellName, ls.CasterID, len(ls.Targets))
}
