package ai

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

// Brain interface defines the core AI functionality
type Brain interface {
	// Core AI methods
	Think() error
	GetBrainType() int8

	// State management
	IsActive() bool
	SetActive(bool)
	GetState() int32
	SetState(int32)

	// Timing
	GetThinkTick() int32
	SetThinkTick(int32)
	GetLastThink() int64
	SetLastThink(int64)

	// Body management
	GetBody() NPC
	SetBody(NPC)

	// Hate management
	AddHate(entityID int32, hate int32)
	GetHate(entityID int32) int32
	ClearHate()
	ClearHateForEntity(entityID int32)
	GetMostHated() int32
	GetHatePercentage(entityID int32) int8
	GetHateList() map[int32]*HateEntry

	// Encounter management
	AddToEncounter(entityID, characterID int32, isPlayer, isBot bool) bool
	IsEntityInEncounter(entityID int32) bool
	IsPlayerInEncounter(characterID int32) bool
	HasPlayerInEncounter() bool
	GetEncounterSize() int
	ClearEncounter()
	CheckLootAllowed(entityID int32) bool

	// Combat methods
	ProcessSpell(target Entity, distance float32) bool
	ProcessMelee(target Entity, distance float32)
	CheckBuffs() bool
	HasRecovered() bool
	MoveCloser(target Spawn)

	// Statistics
	GetStatistics() *BrainStatistics
	ResetStatistics()
}

// BaseBrain provides the default AI implementation
type BaseBrain struct {
	npc           NPC              // The NPC this brain controls
	brainType     int8             // Type of brain
	state         *BrainState      // Brain state management
	hateList      *HateList        // Hate management
	encounterList *EncounterList   // Encounter management
	statistics    *BrainStatistics // Performance statistics
	logger        Logger           // Logger interface
	mutex         sync.RWMutex     // Thread safety
}

// NewBaseBrain creates a new base brain
func NewBaseBrain(npc NPC, logger Logger) *BaseBrain {
	return &BaseBrain{
		npc:           npc,
		brainType:     BrainTypeDefault,
		state:         NewBrainState(),
		hateList:      NewHateList(),
		encounterList: NewEncounterList(),
		statistics:    NewBrainStatistics(),
		logger:        logger,
	}
}

// Think implements the main AI logic
func (bb *BaseBrain) Think() error {
	if !bb.IsActive() {
		return nil
	}

	startTime := time.Now()
	defer func() {
		// Update statistics
		bb.mutex.Lock()
		bb.statistics.ThinkCycles++
		thinkTime := float64(time.Since(startTime).Nanoseconds()) / 1000000.0 // Convert to milliseconds
		bb.statistics.AverageThinkTime = (bb.statistics.AverageThinkTime + thinkTime) / 2.0
		bb.statistics.LastThinkTime = time.Now().UnixMilli()
		bb.mutex.Unlock()

		bb.state.SetLastThink(time.Now().UnixMilli())
	}()

	if bb.npc == nil {
		return fmt.Errorf("brain has no body")
	}

	// Handle pet ID registration for players
	if bb.npc.IsPet() && bb.npc.GetOwner() != nil && bb.npc.GetOwner().IsPlayer() {
		// TODO: Register pet ID with player's info struct
	}

	// Get the most hated target
	mostHatedID := bb.hateList.GetMostHated()
	var target Entity

	if mostHatedID > 0 {
		target = bb.getEntityByID(mostHatedID)
		// Remove dead targets from hate list
		if target != nil && target.GetHP() <= 0 {
			bb.hateList.RemoveHate(mostHatedID)
			target = nil
			// Try again to get most hated
			mostHatedID = bb.hateList.GetMostHated()
			if mostHatedID > 0 {
				target = bb.getEntityByID(mostHatedID)
			}
		}
	}

	// Skip if mezzed, stunned, or feared
	if bb.npc.IsMezzedOrStunned() {
		return nil
	}

	// Get runback distance
	runbackDistance := bb.npc.GetRunbackDistance()

	if target != nil {
		// We have a target to fight
		if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelDetailed {
			bb.logger.LogDebug("NPC %s has target %s", bb.npc.GetName(), target.GetName())
		}

		// Set target if not already set
		if bb.npc.GetTarget() != target {
			bb.npc.SetTarget(target)
		}

		// Face the target
		bb.npc.FaceTarget(target, false)

		// Enter combat if not already in combat
		if !bb.npc.GetInCombat() {
			bb.npc.ClearRunningLocations()
			bb.npc.InCombat(true)
			bb.npc.SetCastOnAggroCompleted(false)
			// TODO: Call spawn script for aggro
		}

		// Check chase distance and water restrictions
		if bb.shouldBreakPursuit(target, runbackDistance) {
			// Break pursuit - clear hate and encounter
			if bb.logger != nil {
				bb.logger.LogDebug("NPC %s breaking pursuit (distance: %.2f)", bb.npc.GetName(), runbackDistance)
			}

			// TODO: Send encounter break messages to players
			bb.hateList.Clear()
			bb.encounterList.Clear()
		} else {
			// Continue combat
			distance := bb.npc.GetDistance(target)

			// Try to cast spells first, then melee
			if !bb.npc.IsCasting() && (!bb.HasRecovered() || !bb.ProcessSpell(target, distance)) {
				if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelDetailed {
					bb.logger.LogDebug("NPC %s attempting melee on %s", bb.npc.GetName(), target.GetName())
				}
				bb.npc.FaceTarget(target, false)
				bb.ProcessMelee(target, distance)
			}
		}
	} else {
		// No target - handle out of combat behavior
		wasInCombat := bb.npc.GetInCombat()

		if bb.npc.GetInCombat() {
			bb.npc.InCombat(false)

			// Restore HP for non-player pets
			if !bb.npc.IsPet() || (bb.npc.IsPet() && bb.npc.GetOwner() != nil && !bb.npc.GetOwner().IsPlayer()) {
				bb.npc.SetHP(bb.npc.GetTotalHP())
			}
		}

		// Check for buffs when not in combat
		bb.CheckBuffs()

		// Handle runback if needed
		if !bb.npc.GetInCombat() && !bb.npc.IsPauseMovementTimerActive() {
			if runbackDistance > RunbackThreshold || (bb.npc.ShouldCallRunback() && !bb.npc.IsFollowing()) {
				bb.npc.SetEncounterState(EncounterStateBroken)
				bb.npc.Runback(runbackDistance)
				bb.npc.SetCallRunback(false)
			} else if bb.npc.GetRunbackLocation() != nil {
				bb.handleRunbackStages()
			}
		}

		// Clear encounter if any entities remain
		if bb.encounterList.Size() > 0 {
			bb.encounterList.Clear()
		}
	}

	return nil
}

// GetBrainType returns the brain type
func (bb *BaseBrain) GetBrainType() int8 {
	return bb.brainType
}

// IsActive returns whether the brain is active
func (bb *BaseBrain) IsActive() bool {
	return bb.state.IsActive()
}

// SetActive sets the brain's active state
func (bb *BaseBrain) SetActive(active bool) {
	bb.state.SetActive(active)
}

// GetState returns the current AI state
func (bb *BaseBrain) GetState() int32 {
	return bb.state.GetState()
}

// SetState sets the current AI state
func (bb *BaseBrain) SetState(state int32) {
	bb.state.SetState(state)
}

// GetThinkTick returns the think tick interval
func (bb *BaseBrain) GetThinkTick() int32 {
	return bb.state.GetThinkTick()
}

// SetThinkTick sets the think tick interval
func (bb *BaseBrain) SetThinkTick(tick int32) {
	bb.state.SetThinkTick(tick)
}

// GetLastThink returns the timestamp of the last think cycle
func (bb *BaseBrain) GetLastThink() int64 {
	return bb.state.GetLastThink()
}

// SetLastThink sets the timestamp of the last think cycle
func (bb *BaseBrain) SetLastThink(timestamp int64) {
	bb.state.SetLastThink(timestamp)
}

// GetBody returns the NPC this brain controls
func (bb *BaseBrain) GetBody() NPC {
	bb.mutex.RLock()
	defer bb.mutex.RUnlock()
	return bb.npc
}

// SetBody sets the NPC this brain controls
func (bb *BaseBrain) SetBody(npc NPC) {
	bb.mutex.Lock()
	defer bb.mutex.Unlock()
	bb.npc = npc
}

// AddHate adds hate for an entity
func (bb *BaseBrain) AddHate(entityID int32, hate int32) {
	// Don't add hate while running back
	if bb.npc != nil && bb.npc.IsRunningBack() {
		return
	}

	// Don't add hate if owner is attacking pet
	if bb.npc != nil && bb.npc.IsPet() && bb.npc.GetOwner() != nil {
		if bb.npc.GetOwner().GetID() == entityID {
			return
		}
	}

	// Check for taunt immunity
	// TODO: Implement immunity checking

	bb.hateList.AddHate(entityID, hate)

	// Update statistics
	bb.mutex.Lock()
	bb.statistics.HateEvents++
	bb.mutex.Unlock()

	// TODO: Add to entity's HatedBy list

	// Add pet owner to hate list if not already present
	entity := bb.getEntityByID(entityID)
	if entity != nil && entity.IsPet() && entity.GetOwner() != nil {
		ownerID := entity.GetOwner().GetID()
		if bb.hateList.GetHate(ownerID) == 0 {
			bb.hateList.AddHate(ownerID, 0)
		}
	}
}

// GetHate returns the hate value for an entity
func (bb *BaseBrain) GetHate(entityID int32) int32 {
	return bb.hateList.GetHate(entityID)
}

// ClearHate removes all hate entries
func (bb *BaseBrain) ClearHate() {
	bb.hateList.Clear()
	// TODO: Update entities' HatedBy lists
}

// ClearHateForEntity removes hate for a specific entity
func (bb *BaseBrain) ClearHateForEntity(entityID int32) {
	bb.hateList.RemoveHate(entityID)
	// TODO: Update entity's HatedBy list
}

// GetMostHated returns the ID of the most hated entity
func (bb *BaseBrain) GetMostHated() int32 {
	return bb.hateList.GetMostHated()
}

// GetHatePercentage returns the hate percentage for an entity
func (bb *BaseBrain) GetHatePercentage(entityID int32) int8 {
	return bb.hateList.GetHatePercentage(entityID)
}

// GetHateList returns a copy of the hate list
func (bb *BaseBrain) GetHateList() map[int32]*HateEntry {
	return bb.hateList.GetAllEntries()
}

// AddToEncounter adds an entity to the encounter list
func (bb *BaseBrain) AddToEncounter(entityID, characterID int32, isPlayer, isBot bool) bool {
	success := bb.encounterList.AddEntity(entityID, characterID, isPlayer, isBot)
	if success {
		bb.mutex.Lock()
		bb.statistics.EncounterEvents++
		bb.mutex.Unlock()
	}
	return success
}

// IsEntityInEncounter checks if an entity is in the encounter
func (bb *BaseBrain) IsEntityInEncounter(entityID int32) bool {
	return bb.encounterList.IsEntityInEncounter(entityID)
}

// IsPlayerInEncounter checks if a player is in the encounter
func (bb *BaseBrain) IsPlayerInEncounter(characterID int32) bool {
	return bb.encounterList.IsPlayerInEncounter(characterID)
}

// HasPlayerInEncounter returns whether any player is in the encounter
func (bb *BaseBrain) HasPlayerInEncounter() bool {
	return bb.encounterList.HasPlayerInEncounter()
}

// GetEncounterSize returns the size of the encounter list
func (bb *BaseBrain) GetEncounterSize() int {
	return bb.encounterList.Size()
}

// ClearEncounter removes all entities from the encounter
func (bb *BaseBrain) ClearEncounter() {
	bb.encounterList.Clear()
	// TODO: Remove spells from NPC
}

// CheckLootAllowed checks if an entity can loot this NPC
func (bb *BaseBrain) CheckLootAllowed(entityID int32) bool {
	// TODO: Implement loot method checking, chest timers, etc.

	// Basic check - is entity in encounter?
	return bb.encounterList.IsEntityInEncounter(entityID)
}

// ProcessSpell attempts to cast a spell
func (bb *BaseBrain) ProcessSpell(target Entity, distance float32) bool {
	if bb.npc == nil {
		return false
	}

	// Check cast percentage and conditions
	castChance := bb.npc.GetCastPercentage()
	if castChance <= 0 {
		return false
	}

	// TODO: Implement random chance checking
	// TODO: Check for stifled, feared conditions

	// Get next spell to cast
	spell := bb.npc.GetNextSpell(target, distance)
	if spell == nil {
		return false
	}

	// Determine spell target
	var spellTarget Spawn = target
	if spell.IsFriendlySpell() {
		// TODO: Find best friendly target (lowest HP group member)
		spellTarget = bb.npc
	}

	// Cast the spell
	success := bb.castSpell(spell, spellTarget, false)
	if success {
		bb.mutex.Lock()
		bb.statistics.SpellsCast++
		bb.mutex.Unlock()
	}

	return success
}

// ProcessMelee handles melee combat
func (bb *BaseBrain) ProcessMelee(target Entity, distance float32) {
	if bb.npc == nil || target == nil {
		return
	}

	maxCombatRange := bb.getMaxCombatRange()

	if distance > maxCombatRange {
		bb.MoveCloser(target)
	} else {
		if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelDetailed {
			bb.logger.LogDebug("NPC %s is within melee range of %s", bb.npc.GetName(), target.GetName())
		}

		// Check if attack is allowed
		if !bb.npc.AttackAllowed(target) {
			return
		}

		currentTime := time.Now().UnixMilli()

		// Primary weapon attack
		if bb.npc.PrimaryWeaponReady() && !bb.npc.IsDazed() && !bb.npc.IsFeared() {
			if bb.logger != nil && bb.state.GetDebugLevel() >= DebugLevelVerbose {
				bb.logger.LogDebug("NPC %s swings primary weapon at %s", bb.npc.GetName(), target.GetName())
			}

			bb.npc.SetPrimaryLastAttackTime(currentTime)
			bb.npc.MeleeAttack(target, distance, true)

			bb.mutex.Lock()
			bb.statistics.MeleeAttacks++
			bb.mutex.Unlock()

			// TODO: Call spawn script for auto attack tick
		}

		// Secondary weapon attack
		if bb.npc.SecondaryWeaponReady() && !bb.npc.IsDazed() {
			bb.npc.SetSecondaryLastAttackTime(currentTime)
			bb.npc.MeleeAttack(target, distance, false)

			bb.mutex.Lock()
			bb.statistics.MeleeAttacks++
			bb.mutex.Unlock()
		}
	}
}

// CheckBuffs checks and casts buff spells
func (bb *BaseBrain) CheckBuffs() bool {
	if bb.npc == nil {
		return false
	}

	// Don't buff in combat, while casting, stunned, etc.
	if bb.npc.GetInCombat() || bb.npc.IsCasting() || bb.npc.IsMezzedOrStunned() ||
		!bb.npc.IsAlive() || bb.npc.IsStifled() || !bb.HasRecovered() {
		return false
	}

	// Get next buff spell
	buffSpell := bb.npc.GetNextBuffSpell(bb.npc)
	if buffSpell == nil {
		return false
	}

	// Try to cast on self first
	if bb.castSpell(buffSpell, bb.npc, false) {
		return true
	}

	// TODO: Try to buff group members

	return false
}

// HasRecovered checks if the brain has recovered from spell casting
func (bb *BaseBrain) HasRecovered() bool {
	return bb.state.HasRecovered()
}

// MoveCloser moves the NPC closer to a target
func (bb *BaseBrain) MoveCloser(target Spawn) {
	if bb.npc == nil || target == nil {
		return
	}

	maxCombatRange := bb.getMaxCombatRange()

	if bb.npc.GetFollowTarget() != target {
		bb.npc.SetFollowTarget(target, maxCombatRange)
	}

	if bb.npc.GetFollowTarget() != nil && !bb.npc.IsFollowing() {
		bb.npc.CalculateRunningLocation(true)
		bb.npc.SetFollowing(true)
	}
}

// GetStatistics returns brain performance statistics
func (bb *BaseBrain) GetStatistics() *BrainStatistics {
	bb.mutex.RLock()
	defer bb.mutex.RUnlock()

	// Return a copy
	return &BrainStatistics{
		ThinkCycles:      bb.statistics.ThinkCycles,
		SpellsCast:       bb.statistics.SpellsCast,
		MeleeAttacks:     bb.statistics.MeleeAttacks,
		HateEvents:       bb.statistics.HateEvents,
		EncounterEvents:  bb.statistics.EncounterEvents,
		AverageThinkTime: bb.statistics.AverageThinkTime,
		LastThinkTime:    bb.statistics.LastThinkTime,
		TotalActiveTime:  bb.statistics.TotalActiveTime,
	}
}

// ResetStatistics resets all performance statistics
func (bb *BaseBrain) ResetStatistics() {
	bb.mutex.Lock()
	defer bb.mutex.Unlock()

	bb.statistics = NewBrainStatistics()
}

// Helper methods

// shouldBreakPursuit checks if the NPC should break pursuit of a target
func (bb *BaseBrain) shouldBreakPursuit(target Entity, runbackDistance float32) bool {
	if target == nil {
		return false
	}

	// Check max chase distance
	maxChase := bb.getMaxChaseDistance()
	if runbackDistance > maxChase {
		return true
	}

	// Check water creature restrictions
	if bb.npc != nil && bb.npc.IsWaterCreature() && !bb.npc.IsFlyingCreature() && !target.InWater() {
		return true
	}

	return false
}

// castSpell casts a spell on a target
func (bb *BaseBrain) castSpell(spell Spell, target Spawn, calculateRunLoc bool) bool {
	if spell == nil || bb.npc == nil {
		return false
	}

	if calculateRunLoc {
		bb.npc.CalculateRunningLocation(true)
	}

	// TODO: Process spell through zone
	// bb.npc.GetZone().ProcessSpell(spell, bb.npc, target)

	// Set spell recovery time
	castTime := spell.GetCastTime() * RecoveryTimeMultiple
	recoveryTime := spell.GetRecoveryTime() * RecoveryTimeMultiple
	totalRecovery := time.Now().UnixMilli() + int64(castTime) + int64(recoveryTime) + int64(SpellRecoveryBuffer)

	bb.state.SetSpellRecovery(totalRecovery)

	return true
}

// handleRunbackStages handles the various stages of runback
func (bb *BaseBrain) handleRunbackStages() {
	if bb.npc == nil {
		return
	}

	runbackLoc := bb.npc.GetRunbackLocation()
	if runbackLoc == nil {
		return
	}

	// TODO: Implement runback stage handling
	// This would involve movement management and position updates
}

// getEntityByID retrieves an entity by ID (placeholder)
func (bb *BaseBrain) getEntityByID(entityID int32) Entity {
	// TODO: Implement entity lookup through zone
	return nil
}

// getMaxChaseDistance returns the maximum chase distance
func (bb *BaseBrain) getMaxChaseDistance() float32 {
	// TODO: Check NPC info struct and zone rules
	return MaxChaseDistance
}

// getMaxCombatRange returns the maximum combat range
func (bb *BaseBrain) getMaxCombatRange() float32 {
	// TODO: Check zone rules
	return MaxCombatRange
}
