package spells

import (
	"fmt"
	"sync"
	"time"
)

// InterruptStruct represents a spell interruption event
type InterruptStruct struct {
	InterruptedEntityID int32     // ID of the entity being interrupted
	SpellID             int32     // ID of the spell being interrupted
	ErrorCode           int16     // Error code for the interruption
	FromMovement        bool      // Whether interruption was caused by movement
	Canceled            bool      // Whether the spell was canceled vs interrupted
	Timestamp           time.Time // When the interrupt occurred
}

// CastTimer represents a spell casting timer
type CastTimer struct {
	CasterID        int32         // ID of the entity casting
	TargetID        int32         // ID of the target
	SpellID         int32         // ID of the spell being cast
	ZoneID          int32         // ID of the zone where casting occurs
	StartTime       time.Time     // When casting started
	Duration        time.Duration // How long the cast takes
	InHeroicOpp     bool          // Whether this is part of a heroic opportunity
	DeleteTimer     bool          // Flag to mark timer for deletion
	IsEntityCommand bool          // Whether this is an entity command vs spell

	mutex sync.RWMutex // Thread safety
}

// RecastTimer represents a spell recast cooldown timer
type RecastTimer struct {
	CasterID         int32         // ID of the entity with the recast
	ClientID         int32         // ID of the client (if player)
	SpellID          int32         // ID of the spell on cooldown
	LinkedTimerID    int32         // ID of linked timer group
	TypeGroupSpellID int32         // Type group for timer sharing
	StartTime        time.Time     // When the recast started
	Duration         time.Duration // How long the recast lasts
	StayLocked       bool          // Whether spell stays locked after recast

	mutex sync.RWMutex // Thread safety
}

// CastSpell represents a spell casting request
type CastSpell struct {
	CasterID int32 // ID of the entity casting
	TargetID int32 // ID of the target
	SpellID  int32 // ID of the spell to cast
	ZoneID   int32 // ID of the zone
}

// SpellQueue represents a player's spell queue entry
type SpellQueueEntry struct {
	SpellID     int32     // ID of the queued spell
	Priority    int32     // Queue priority
	QueuedTime  time.Time // When the spell was queued
	TargetID    int32     // Target for the spell
	HostileOnly bool      // Whether this is a hostile-only queue
}

// HeroicOpportunity represents a heroic opportunity instance
type HeroicOpportunity struct {
	ID          int32         // Unique identifier
	InitiatorID int32         // ID of the player/group that started it
	TargetID    int32         // ID of the target
	StartTime   time.Time     // When the HO started
	Duration    time.Duration // Total time allowed
	CurrentStep int32         // Current step in the sequence
	TotalSteps  int32         // Total steps in the sequence
	IsGroup     bool          // Whether this is a group HO
	Complete    bool          // Whether the HO completed successfully
	WheelID     int32         // ID of the wheel type

	mutex sync.RWMutex // Thread safety
}

// SpellProcess manages all spell casting for a zone
type SpellProcess struct {
	// Core collections
	activeSpells   map[int32]*LuaSpell          // Active spells by spell instance ID
	castTimers     []*CastTimer                 // Active cast timers
	recastTimers   []*RecastTimer               // Active recast timers
	interruptQueue []*InterruptStruct           // Queued interruptions
	spellQueues    map[int32][]*SpellQueueEntry // Player spell queues by player ID

	// Heroic Opportunities
	soloHeroicOps  map[int32]*HeroicOpportunity // Solo HOs by client ID
	groupHeroicOps map[int32]*HeroicOpportunity // Group HOs by group ID

	// Targeting and removal
	removeTargetList map[int32][]int32 // Targets to remove by spell ID
	spellCancelList  []int32           // Spells marked for cancellation

	// State management
	lastProcessTime time.Time // Last time Process() was called
	nextSpellID     int32     // Next available spell instance ID

	// Thread safety
	mutex sync.RWMutex // Main process mutex

	// TODO: Add when other systems are available
	// zoneServer      *ZoneServer              // Reference to zone server
	// luaInterface    *LuaInterface            // Reference to Lua interface
}

// NewSpellProcess creates a new spell process instance
func NewSpellProcess() *SpellProcess {
	return &SpellProcess{
		activeSpells:     make(map[int32]*LuaSpell),
		castTimers:       make([]*CastTimer, 0),
		recastTimers:     make([]*RecastTimer, 0),
		interruptQueue:   make([]*InterruptStruct, 0),
		spellQueues:      make(map[int32][]*SpellQueueEntry),
		soloHeroicOps:    make(map[int32]*HeroicOpportunity),
		groupHeroicOps:   make(map[int32]*HeroicOpportunity),
		removeTargetList: make(map[int32][]int32),
		spellCancelList:  make([]int32, 0),
		lastProcessTime:  time.Now(),
		nextSpellID:      1,
	}
}

// Process handles the main spell processing loop
func (sp *SpellProcess) Process() {
	sp.mutex.Lock()
	defer sp.mutex.Unlock()

	now := time.Now()
	// Only process every 50ms to match C++ implementation
	if now.Sub(sp.lastProcessTime) < time.Duration(ProcessCheckInterval)*time.Millisecond {
		return
	}
	sp.lastProcessTime = now

	// Process active spells (duration checks, ticks)
	sp.processActiveSpells(now)

	// Process spell cancellations
	sp.processSpellCancellations()

	// Process interrupts
	sp.processInterrupts()

	// Process cast timers
	sp.processCastTimers(now)

	// Process recast timers
	sp.processRecastTimers(now)

	// Process spell queues
	sp.processSpellQueues()

	// Process heroic opportunities
	sp.processHeroicOpportunities(now)
}

// processActiveSpells handles duration checks and spell ticks
func (sp *SpellProcess) processActiveSpells(now time.Time) {
	expiredSpells := make([]int32, 0)

	for spellID, luaSpell := range sp.activeSpells {
		if luaSpell == nil {
			expiredSpells = append(expiredSpells, spellID)
			continue
		}

		// Check if spell duration has expired
		// TODO: Implement proper duration checking based on spell data
		// This would check luaSpell.spell.GetSpellData().duration1 etc.

		// Check if spell needs to tick
		// TODO: Implement spell tick processing
		// This would call ProcessSpell(luaSpell, false) for tick effects
	}

	// Remove expired spells
	for _, spellID := range expiredSpells {
		sp.deleteCasterSpell(spellID, "expired")
	}
}

// processSpellCancellations handles spells marked for cancellation
func (sp *SpellProcess) processSpellCancellations() {
	if len(sp.spellCancelList) == 0 {
		return
	}

	canceledSpells := make([]int32, len(sp.spellCancelList))
	copy(canceledSpells, sp.spellCancelList)
	sp.spellCancelList = sp.spellCancelList[:0] // Clear the list

	for _, spellID := range canceledSpells {
		sp.deleteCasterSpell(spellID, "canceled")
	}
}

// processInterrupts handles queued spell interruptions
func (sp *SpellProcess) processInterrupts() {
	if len(sp.interruptQueue) == 0 {
		return
	}

	interrupts := make([]*InterruptStruct, len(sp.interruptQueue))
	copy(interrupts, sp.interruptQueue)
	sp.interruptQueue = sp.interruptQueue[:0] // Clear the queue

	for _, interrupt := range interrupts {
		sp.checkInterrupt(interrupt)
	}
}

// processCastTimers handles spell casting completion
func (sp *SpellProcess) processCastTimers(now time.Time) {
	completedTimers := make([]*CastTimer, 0)
	remainingTimers := make([]*CastTimer, 0)

	for _, timer := range sp.castTimers {
		if timer.DeleteTimer {
			// Timer marked for deletion
			continue
		}

		if now.Sub(timer.StartTime) >= timer.Duration {
			// Cast time completed
			timer.DeleteTimer = true
			completedTimers = append(completedTimers, timer)

			// TODO: Send finish cast packet to client
			// TODO: Call CastProcessedSpell or CastProcessedEntityCommand
		} else {
			remainingTimers = append(remainingTimers, timer)
		}
	}

	sp.castTimers = remainingTimers
}

// processRecastTimers handles spell cooldown expiration
func (sp *SpellProcess) processRecastTimers(now time.Time) {
	expiredTimers := make([]*RecastTimer, 0)
	remainingTimers := make([]*RecastTimer, 0)

	for _, timer := range sp.recastTimers {
		if now.Sub(timer.StartTime) >= timer.Duration {
			// Recast timer expired
			expiredTimers = append(expiredTimers, timer)

			// TODO: Unlock spell for the caster if not a maintained effect
			// TODO: Send spell book update to client
		} else {
			remainingTimers = append(remainingTimers, timer)
		}
	}

	sp.recastTimers = remainingTimers
}

// processSpellQueues handles queued spells for players
func (sp *SpellProcess) processSpellQueues() {
	for playerID, queue := range sp.spellQueues {
		if len(queue) == 0 {
			continue
		}

		// TODO: Check if player is casting and can cast next spell
		// TODO: Process highest priority spell from queue
		// This would call ProcessSpell for the queued spell

		_ = playerID // Placeholder to avoid unused variable error
	}
}

// processHeroicOpportunities handles heroic opportunity timers
func (sp *SpellProcess) processHeroicOpportunities(now time.Time) {
	// Process solo heroic opportunities
	expiredSolo := make([]int32, 0)
	for clientID, ho := range sp.soloHeroicOps {
		if now.Sub(ho.StartTime) >= ho.Duration {
			ho.Complete = true
			expiredSolo = append(expiredSolo, clientID)
			// TODO: Send heroic opportunity update packet
		}
	}
	for _, clientID := range expiredSolo {
		delete(sp.soloHeroicOps, clientID)
	}

	// Process group heroic opportunities
	expiredGroup := make([]int32, 0)
	for groupID, ho := range sp.groupHeroicOps {
		if now.Sub(ho.StartTime) >= ho.Duration {
			ho.Complete = true
			expiredGroup = append(expiredGroup, groupID)
			// TODO: Send heroic opportunity update to all group members
		}
	}
	for _, groupID := range expiredGroup {
		delete(sp.groupHeroicOps, groupID)
	}
}

// RemoveCaster removes references to a caster when they are destroyed
func (sp *SpellProcess) RemoveCaster(casterID int32) {
	sp.mutex.Lock()
	defer sp.mutex.Unlock()

	// Remove from active spells
	expiredSpells := make([]int32, 0)
	for spellID, luaSpell := range sp.activeSpells {
		if luaSpell != nil && luaSpell.CasterID == casterID {
			luaSpell.CasterID = 0 // Clear caster reference
			expiredSpells = append(expiredSpells, spellID)
		}
	}

	// Clean up spells with invalid casters
	for _, spellID := range expiredSpells {
		sp.deleteCasterSpell(spellID, "caster removed")
	}

	// Remove cast timers for this caster
	remainingCastTimers := make([]*CastTimer, 0)
	for _, timer := range sp.castTimers {
		if timer.CasterID != casterID {
			remainingCastTimers = append(remainingCastTimers, timer)
		}
	}
	sp.castTimers = remainingCastTimers

	// Remove recast timers for this caster
	remainingRecastTimers := make([]*RecastTimer, 0)
	for _, timer := range sp.recastTimers {
		if timer.CasterID != casterID {
			remainingRecastTimers = append(remainingRecastTimers, timer)
		}
	}
	sp.recastTimers = remainingRecastTimers

	// Remove spell queue for this caster
	delete(sp.spellQueues, casterID)
}

// Interrupt creates an interrupt request for a casting entity
func (sp *SpellProcess) Interrupt(entityID int32, spellID int32, errorCode int16, cancel, fromMovement bool) {
	sp.mutex.Lock()
	defer sp.mutex.Unlock()

	interrupt := &InterruptStruct{
		InterruptedEntityID: entityID,
		SpellID:             spellID,
		ErrorCode:           errorCode,
		FromMovement:        fromMovement,
		Canceled:            cancel,
		Timestamp:           time.Now(),
	}

	sp.interruptQueue = append(sp.interruptQueue, interrupt)
}

// checkInterrupt processes a single interrupt
func (sp *SpellProcess) checkInterrupt(interrupt *InterruptStruct) {
	if interrupt == nil {
		return
	}

	// TODO: Implement interrupt processing
	// This would:
	// 1. Find the casting entity
	// 2. Send finish cast packet if needed
	// 3. Remove spell timers from spawn
	// 4. Set entity casting state to false
	// 5. Send interrupt packet to zone
	// 6. Send spell failed packet if error code > 0
	// 7. Unlock spell for player
	// 8. Send spell book update

	fmt.Printf("Processing interrupt for entity %d, spell %d, error %d\n",
		interrupt.InterruptedEntityID, interrupt.SpellID, interrupt.ErrorCode)
}

// IsReady checks if an entity can cast a spell (not on recast)
func (sp *SpellProcess) IsReady(spellID, casterID int32) bool {
	sp.mutex.RLock()
	defer sp.mutex.RUnlock()

	// TODO: Check if caster is currently casting
	// if caster.IsCasting() { return false }

	// Check recast timers
	for _, timer := range sp.recastTimers {
		if timer.SpellID == spellID && timer.CasterID == casterID {
			return false // Still on cooldown
		}
	}

	return true
}

// AddSpellToQueue adds a spell to a player's casting queue
func (sp *SpellProcess) AddSpellToQueue(spellID, casterID, targetID int32, priority int32) {
	sp.mutex.Lock()
	defer sp.mutex.Unlock()

	entry := &SpellQueueEntry{
		SpellID:    spellID,
		Priority:   priority,
		QueuedTime: time.Now(),
		TargetID:   targetID,
	}

	if sp.spellQueues[casterID] == nil {
		sp.spellQueues[casterID] = make([]*SpellQueueEntry, 0)
	}

	// Add to queue (TODO: sort by priority)
	sp.spellQueues[casterID] = append(sp.spellQueues[casterID], entry)

	// Limit queue size
	if len(sp.spellQueues[casterID]) > MaxQueuedSpells {
		sp.spellQueues[casterID] = sp.spellQueues[casterID][1:] // Remove oldest
	}
}

// RemoveSpellFromQueue removes a specific spell from a player's queue
func (sp *SpellProcess) RemoveSpellFromQueue(spellID, casterID int32) bool {
	sp.mutex.Lock()
	defer sp.mutex.Unlock()

	queue, exists := sp.spellQueues[casterID]
	if !exists {
		return false
	}

	for i, entry := range queue {
		if entry.SpellID == spellID {
			// Remove entry from queue
			sp.spellQueues[casterID] = append(queue[:i], queue[i+1:]...)
			return true
		}
	}

	return false
}

// ClearSpellQueue clears a player's spell queue
func (sp *SpellProcess) ClearSpellQueue(casterID int32, hostileOnly bool) {
	sp.mutex.Lock()
	defer sp.mutex.Unlock()

	if !hostileOnly {
		delete(sp.spellQueues, casterID)
		return
	}

	// TODO: Remove only hostile spells
	// This would require checking spell data to determine if spell is hostile
}

// AddSpellCancel marks a spell for cancellation
func (sp *SpellProcess) AddSpellCancel(spellID int32) {
	sp.mutex.Lock()
	defer sp.mutex.Unlock()

	sp.spellCancelList = append(sp.spellCancelList, spellID)
}

// deleteCasterSpell removes a spell from active processing
func (sp *SpellProcess) deleteCasterSpell(spellID int32, reason string) bool {
	luaSpell, exists := sp.activeSpells[spellID]
	if !exists {
		return false
	}

	// TODO: Implement proper spell removal
	// This would:
	// 1. Handle concentration return for toggle spells
	// 2. Check recast for non-duration-until-cancel spells
	// 3. Unlock spell for player
	// 4. Remove procs from caster
	// 5. Remove maintained spell from caster
	// 6. Remove targets from spell
	// 7. Process spell removal effects

	fmt.Printf("Removing spell %d, reason: %s\n", spellID, reason)

	delete(sp.activeSpells, spellID)

	// Clean up removal targets list
	delete(sp.removeTargetList, spellID)

	_ = luaSpell // Placeholder to avoid unused variable error

	return true
}

// GetActiveSpellCount returns the number of active spells
func (sp *SpellProcess) GetActiveSpellCount() int {
	sp.mutex.RLock()
	defer sp.mutex.RUnlock()

	return len(sp.activeSpells)
}

// GetQueuedSpellCount returns the number of queued spells for a player
func (sp *SpellProcess) GetQueuedSpellCount(casterID int32) int {
	sp.mutex.RLock()
	defer sp.mutex.RUnlock()

	if queue, exists := sp.spellQueues[casterID]; exists {
		return len(queue)
	}

	return 0
}

// GetRecastTimeRemaining returns remaining recast time for a spell
func (sp *SpellProcess) GetRecastTimeRemaining(spellID, casterID int32) time.Duration {
	sp.mutex.RLock()
	defer sp.mutex.RUnlock()

	for _, timer := range sp.recastTimers {
		if timer.SpellID == spellID && timer.CasterID == casterID {
			elapsed := time.Since(timer.StartTime)
			if elapsed >= timer.Duration {
				return 0
			}
			return timer.Duration - elapsed
		}
	}

	return 0
}

// RemoveAllSpells removes all spells from processing (used during shutdown)
func (sp *SpellProcess) RemoveAllSpells(reloadSpells bool) {
	sp.mutex.Lock()
	defer sp.mutex.Unlock()

	// Clear all spell collections
	if reloadSpells {
		// Keep some data for reload
		sp.activeSpells = make(map[int32]*LuaSpell)
	} else {
		// Complete cleanup
		for spellID := range sp.activeSpells {
			sp.deleteCasterSpell(spellID, "shutdown")
		}
		sp.activeSpells = make(map[int32]*LuaSpell)
	}

	sp.castTimers = make([]*CastTimer, 0)
	sp.recastTimers = make([]*RecastTimer, 0)
	sp.interruptQueue = make([]*InterruptStruct, 0)
	sp.spellQueues = make(map[int32][]*SpellQueueEntry)
	sp.soloHeroicOps = make(map[int32]*HeroicOpportunity)
	sp.groupHeroicOps = make(map[int32]*HeroicOpportunity)
	sp.removeTargetList = make(map[int32][]int32)
	sp.spellCancelList = make([]int32, 0)
}

// NewCastTimer creates a new cast timer
func (ct *CastTimer) IsExpired() bool {
	ct.mutex.RLock()
	defer ct.mutex.RUnlock()

	return time.Since(ct.StartTime) >= ct.Duration
}

// NewRecastTimer creates a new recast timer
func (rt *RecastTimer) IsExpired() bool {
	rt.mutex.RLock()
	defer rt.mutex.RUnlock()

	return time.Since(rt.StartTime) >= rt.Duration
}

// GetRemainingTime returns remaining time on the recast
func (rt *RecastTimer) GetRemainingTime() time.Duration {
	rt.mutex.RLock()
	defer rt.mutex.RUnlock()

	elapsed := time.Since(rt.StartTime)
	if elapsed >= rt.Duration {
		return 0
	}
	return rt.Duration - elapsed
}
