package npc

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

	"eq2emu/internal/common"
	"eq2emu/internal/entity"
	"eq2emu/internal/spawn"
)

// NewNPC creates a new NPC with default values
func NewNPC() *NPC {
	npc := &NPC{
		Entity:                entity.NewEntity(),
		appearanceID:          0,
		npcID:                 0,
		aiStrategy:            AIStrategyBalanced,
		attackType:            0,
		castPercentage:        DefaultCastPercentage,
		maxPetLevel:           DefaultMaxPetLevel,
		aggroRadius:           DefaultAggroRadius,
		baseAggroRadius:       DefaultAggroRadius,
		runback:               nil,
		runningBack:           false,
		runbackHeadingDir1:    0,
		runbackHeadingDir2:    0,
		pauseTimer:            NewTimer(),
		primarySpellList:      0,
		secondarySpellList:    0,
		primarySkillList:      0,
		secondarySkillList:    0,
		equipmentListID:       0,
		skills:                make(map[string]*Skill),
		spells:                make([]*NPCSpell, 0),
		castOnSpells:          make(map[int8][]*NPCSpell),
		skillBonuses:          make(map[int32]*SkillBonus),
		hasSpells:             false,
		castOnAggroCompleted:  false,
		shardID:               0,
		shardCharID:           0,
		shardCreatedTimestamp: 0,
		callRunback:           false,
	}

	// Initialize cast-on spell arrays
	npc.castOnSpells[CastOnSpawn] = make([]*NPCSpell, 0)
	npc.castOnSpells[CastOnAggro] = make([]*NPCSpell, 0)

	// Create default brain
	npc.brain = NewDefaultBrain(npc)

	return npc
}

// NewNPCFromExisting creates a copy of an existing NPC with randomization
func NewNPCFromExisting(oldNPC *NPC) *NPC {
	if oldNPC == nil {
		return NewNPC()
	}

	npc := NewNPC()

	// Copy basic properties
	npc.npcID = oldNPC.npcID
	npc.appearanceID = oldNPC.appearanceID
	npc.aiStrategy = oldNPC.aiStrategy
	npc.attackType = oldNPC.attackType
	npc.castPercentage = oldNPC.castPercentage
	npc.maxPetLevel = oldNPC.maxPetLevel
	npc.baseAggroRadius = oldNPC.baseAggroRadius
	npc.aggroRadius = oldNPC.baseAggroRadius

	// Copy spell lists
	npc.primarySpellList = oldNPC.primarySpellList
	npc.secondarySpellList = oldNPC.secondarySpellList
	npc.primarySkillList = oldNPC.primarySkillList
	npc.secondarySkillList = oldNPC.secondarySkillList
	npc.equipmentListID = oldNPC.equipmentListID

	// Copy entity data (stats, appearance, etc.)
	if oldNPC.Entity != nil {
		npc.Entity = oldNPC.Entity.Copy().(*entity.Entity)
	}

	// Handle level randomization
	if oldNPC.Entity != nil {
		minLevel := oldNPC.Entity.GetMinLevel()
		maxLevel := oldNPC.Entity.GetMaxLevel()
		if minLevel < maxLevel {
			randomLevel := minLevel + int8(rand.Intn(int(maxLevel-minLevel)+1))
			npc.Entity.SetLevel(randomLevel)
		}
	}

	// Copy skills (deep copy)
	npc.copySkills(oldNPC)

	// Copy spells (deep copy)
	npc.copySpells(oldNPC)

	// Handle appearance randomization
	if oldNPC.Entity != nil && oldNPC.Entity.GetRandomize() > 0 {
		npc.randomizeAppearance(oldNPC.Entity.GetRandomize())
	}

	return npc
}

// IsNPC always returns true for NPC instances
func (n *NPC) IsNPC() bool {
	return true
}

// GetAppearanceID returns the appearance ID
func (n *NPC) GetAppearanceID() int32 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.appearanceID
}

// SetAppearanceID sets the appearance ID
func (n *NPC) SetAppearanceID(id int32) {
	n.mutex.Lock()
	defer n.mutex.Unlock()
	n.appearanceID = id
}

// GetNPCID returns the NPC database ID
func (n *NPC) GetNPCID() int32 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.npcID
}

// SetNPCID sets the NPC database ID
func (n *NPC) SetNPCID(id int32) {
	n.mutex.Lock()
	defer n.mutex.Unlock()
	n.npcID = id
}

// AI Strategy methods
func (n *NPC) GetAIStrategy() int8 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.aiStrategy
}

func (n *NPC) SetAIStrategy(strategy int8) {
	n.mutex.Lock()
	defer n.mutex.Unlock()
	n.aiStrategy = strategy
}

// Attack Type methods
func (n *NPC) GetAttackType() int8 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.attackType
}

func (n *NPC) SetAttackType(attackType int8) {
	n.mutex.Lock()
	defer n.mutex.Unlock()
	n.attackType = attackType
}

// Cast Percentage methods
func (n *NPC) GetCastPercentage() int8 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.castPercentage
}

func (n *NPC) SetCastPercentage(percentage int8) {
	n.mutex.Lock()
	defer n.mutex.Unlock()
	if percentage < 0 {
		percentage = 0
	} else if percentage > 100 {
		percentage = 100
	}
	n.castPercentage = percentage
}

// Aggro Radius methods
func (n *NPC) GetAggroRadius() float32 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.aggroRadius
}

func (n *NPC) SetAggroRadius(radius float32, overrideBase bool) {
	n.mutex.Lock()
	defer n.mutex.Unlock()

	if n.baseAggroRadius == 0.0 || overrideBase {
		n.baseAggroRadius = radius
	}
	n.aggroRadius = radius
}

func (n *NPC) GetBaseAggroRadius() float32 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.baseAggroRadius
}

// Pet Level methods
func (n *NPC) GetMaxPetLevel() int8 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.maxPetLevel
}

func (n *NPC) SetMaxPetLevel(level int8) {
	n.mutex.Lock()
	defer n.mutex.Unlock()
	n.maxPetLevel = level
}

// Spell List methods
func (n *NPC) GetPrimarySpellList() int32 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.primarySpellList
}

func (n *NPC) SetPrimarySpellList(id int32) {
	n.mutex.Lock()
	defer n.mutex.Unlock()
	n.primarySpellList = id
}

func (n *NPC) GetSecondarySpellList() int32 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.secondarySpellList
}

func (n *NPC) SetSecondarySpellList(id int32) {
	n.mutex.Lock()
	defer n.mutex.Unlock()
	n.secondarySpellList = id
}

// Skill List methods
func (n *NPC) GetPrimarySkillList() int32 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.primarySkillList
}

func (n *NPC) SetPrimarySkillList(id int32) {
	n.mutex.Lock()
	defer n.mutex.Unlock()
	n.primarySkillList = id
}

func (n *NPC) GetSecondarySkillList() int32 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.secondarySkillList
}

func (n *NPC) SetSecondarySkillList(id int32) {
	n.mutex.Lock()
	defer n.mutex.Unlock()
	n.secondarySkillList = id
}

// Equipment List methods
func (n *NPC) GetEquipmentListID() int32 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.equipmentListID
}

func (n *NPC) SetEquipmentListID(id int32) {
	n.mutex.Lock()
	defer n.mutex.Unlock()
	n.equipmentListID = id
}

// HasSpells returns whether the NPC has any spells
func (n *NPC) HasSpells() bool {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.hasSpells
}

// GetSpells returns a copy of all spells
func (n *NPC) GetSpells() []*NPCSpell {
	n.mutex.RLock()
	defer n.mutex.RUnlock()

	result := make([]*NPCSpell, len(n.spells))
	for i, spell := range n.spells {
		result[i] = spell.Copy()
	}
	return result
}

// SetSpells sets the NPC's spell list
func (n *NPC) SetSpells(spells []*NPCSpell) {
	n.mutex.Lock()
	defer n.mutex.Unlock()

	// Clear existing cast-on spells
	for i := int8(0); i < MaxCastTypes; i++ {
		n.castOnSpells[i] = make([]*NPCSpell, 0)
	}

	// Clear existing spells
	n.spells = make([]*NPCSpell, 0)

	if spells == nil || len(spells) == 0 {
		n.hasSpells = false
		return
	}

	n.hasSpells = true

	// Process spells and separate cast-on types
	for _, spell := range spells {
		if spell == nil {
			continue
		}

		spellCopy := spell.Copy()

		if spellCopy.GetCastOnSpawn() {
			n.castOnSpells[CastOnSpawn] = append(n.castOnSpells[CastOnSpawn], spellCopy)
		} else if spellCopy.GetCastOnInitialAggro() {
			n.castOnSpells[CastOnAggro] = append(n.castOnSpells[CastOnAggro], spellCopy)
		} else {
			n.spells = append(n.spells, spellCopy)
		}
	}
}

// GetSkillByName returns a skill by name
func (n *NPC) GetSkillByName(name string, checkUpdate bool) *Skill {
	n.mutex.RLock()
	defer n.mutex.RUnlock()

	skill, exists := n.skills[name]
	if !exists {
		return nil
	}

	// Random skill increase (10% chance)
	if checkUpdate && skill.GetCurrentVal() < skill.MaxVal && rand.Intn(100) >= 90 {
		skill.IncreaseSkill()
	}

	return skill
}

// GetSkillByID returns a skill by ID (requires master skill list lookup)
func (n *NPC) GetSkillByID(id int32, checkUpdate bool) *Skill {
	// TODO: Implement skill lookup by ID using master skill list
	// For now, return nil as we need the master skill list integration
	return nil
}

// SetSkills sets the NPC's skills
func (n *NPC) SetSkills(skills map[string]*Skill) {
	n.mutex.Lock()
	defer n.mutex.Unlock()

	// Clear existing skills
	n.skills = make(map[string]*Skill)

	// Copy skills
	if skills != nil {
		for name, skill := range skills {
			if skill != nil {
				n.skills[name] = &Skill{
					SkillID:    skill.SkillID,
					Name:       skill.Name,
					CurrentVal: skill.CurrentVal,
					MaxVal:     skill.MaxVal,
				}
			}
		}
	}
}

// AddSkillBonus adds a skill bonus from a spell
func (n *NPC) AddSkillBonus(spellID, skillID int32, value float32) {
	if value == 0 {
		return
	}

	n.mutex.Lock()
	defer n.mutex.Unlock()

	// Get or create skill bonus
	skillBonus, exists := n.skillBonuses[spellID]
	if !exists {
		skillBonus = NewSkillBonus(spellID)
		n.skillBonuses[spellID] = skillBonus
	}

	// Add the skill bonus
	skillBonus.AddSkill(skillID, value)

	// Apply bonus to existing skills
	for _, skill := range n.skills {
		if skill.SkillID == skillID {
			skill.CurrentVal += int16(value)
			skill.MaxVal += int16(value)
		}
	}
}

// RemoveSkillBonus removes skill bonuses from a spell
func (n *NPC) RemoveSkillBonus(spellID int32) {
	n.mutex.Lock()
	defer n.mutex.Unlock()

	skillBonus, exists := n.skillBonuses[spellID]
	if !exists {
		return
	}

	// Remove bonuses from skills
	bonuses := skillBonus.GetSkills()
	for _, bonus := range bonuses {
		for _, skill := range n.skills {
			if skill.SkillID == bonus.SkillID {
				skill.CurrentVal -= int16(bonus.Value)
				skill.MaxVal -= int16(bonus.Value)
			}
		}
	}

	// Remove the skill bonus
	delete(n.skillBonuses, spellID)
}

// Runback Location methods
func (n *NPC) SetRunbackLocation(x, y, z float32, gridID int32, resetHP bool) {
	n.mutex.Lock()
	defer n.mutex.Unlock()

	n.runback = &MovementLocation{
		X:                x,
		Y:                y,
		Z:                z,
		GridID:           gridID,
		Stage:            0,
		ResetHPOnRunback: resetHP,
		UseNavPath:       false,
		Mapped:           false,
	}
}

func (n *NPC) GetRunbackLocation() *MovementLocation {
	n.mutex.RLock()
	defer n.mutex.RUnlock()

	if n.runback == nil {
		return nil
	}
	return n.runback.Copy()
}

func (n *NPC) GetRunbackDistance() float32 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()

	if n.runback == nil || n.Entity == nil {
		return 0
	}

	// Calculate distance using basic distance formula
	dx := n.Entity.GetX() - n.runback.X
	dy := n.Entity.GetY() - n.runback.Y
	dz := n.Entity.GetZ() - n.runback.Z

	return float32(math.Sqrt(float64(dx*dx + dy*dy + dz*dz)))
}

func (n *NPC) ClearRunback() {
	n.mutex.Lock()
	defer n.mutex.Unlock()

	n.runback = nil
	n.runningBack = false
	n.runbackHeadingDir1 = 0
	n.runbackHeadingDir2 = 0
}

// StartRunback sets the current location as the runback point
func (n *NPC) StartRunback(resetHP bool) {
	if n.GetRunbackLocation() != nil {
		return
	}

	if n.Entity == nil {
		return
	}

	n.mutex.Lock()
	defer n.mutex.Unlock()

	n.runback = &MovementLocation{
		X:                n.Entity.GetX(),
		Y:                n.Entity.GetY(),
		Z:                n.Entity.GetZ(),
		GridID:           n.Entity.GetLocation(),
		Stage:            0,
		ResetHPOnRunback: resetHP,
		UseNavPath:       false,
		Mapped:           false,
	}

	// Store original heading
	n.runbackHeadingDir1 = n.Entity.GetHeading()
	n.runbackHeadingDir2 = n.Entity.GetHeading() // In C++ these are separate values
}

// Runback initiates runback movement
func (n *NPC) Runback(distance float32, stopFollowing bool) {
	if n.runback == nil {
		return
	}

	if distance == 0.0 {
		distance = n.GetRunbackDistance()
	}

	n.mutex.Lock()
	n.runningBack = true
	n.mutex.Unlock()

	// TODO: Implement actual movement logic
	// This would integrate with the movement system

	if stopFollowing && n.Entity != nil {
		n.Entity.SetFollowing(false)
	}
}

// IsRunningBack returns whether the NPC is currently running back
func (n *NPC) IsRunningBack() bool {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.runningBack
}

// Movement pause methods
func (n *NPC) PauseMovement(periodMS int32) bool {
	if periodMS < 1 {
		periodMS = 1
	}

	if periodMS > MaxPauseTime {
		periodMS = MaxPauseTime
	}

	// TODO: Integrate with movement system to stop movement
	// For now, just start the pause timer
	n.pauseTimer.Start(periodMS, true)

	return true
}

func (n *NPC) IsPauseMovementTimerActive() bool {
	if n.pauseTimer.Check() {
		n.pauseTimer.Disable()
		n.callRunback = true
	}

	return n.pauseTimer.Enabled()
}

// Brain methods
func (n *NPC) GetBrain() Brain {
	n.brainMutex.RLock()
	defer n.brainMutex.RUnlock()
	return n.brain
}

func (n *NPC) SetBrain(brain Brain) {
	n.brainMutex.Lock()
	defer n.brainMutex.Unlock()

	// Validate brain matches this NPC
	if brain != nil && brain.GetBody() != n {
		// TODO: Log error
		return
	}

	n.brain = brain
}

// Shard methods
func (n *NPC) GetShardID() int32 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.shardID
}

func (n *NPC) SetShardID(id int32) {
	n.mutex.Lock()
	defer n.mutex.Unlock()
	n.shardID = id
}

func (n *NPC) GetShardCharID() int32 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.shardCharID
}

func (n *NPC) SetShardCharID(id int32) {
	n.mutex.Lock()
	defer n.mutex.Unlock()
	n.shardCharID = id
}

func (n *NPC) GetShardCreatedTimestamp() int64 {
	n.mutex.RLock()
	defer n.mutex.RUnlock()
	return n.shardCreatedTimestamp
}

func (n *NPC) SetShardCreatedTimestamp(timestamp int64) {
	n.mutex.Lock()
	defer n.mutex.Unlock()
	n.shardCreatedTimestamp = timestamp
}

// HandleUse processes entity command usage
func (n *NPC) HandleUse(client Client, commandType string) bool {
	if client == nil || len(commandType) == 0 {
		return false
	}

	// Check if NPC shows command icons
	if n.Entity == nil {
		return false
	}

	// TODO: Implement entity command processing
	// This would integrate with the command system

	return false
}

// InCombat handles combat state changes
func (n *NPC) InCombat(val bool) {
	if n.Entity == nil {
		return
	}

	currentCombat := n.Entity.GetInCombat()
	if currentCombat == val {
		return
	}

	n.Entity.SetInCombat(val)

	if val {
		// Entering combat
		if n.GetRunbackLocation() == nil {
			n.StartRunback(true)
		}

		// Set max speed for combat
		if n.Entity.GetMaxSpeed() > 0 {
			n.Entity.SetSpeed(n.Entity.GetMaxSpeed())
		}

		// TODO: Add combat icon, call spawn scripts, etc.

	} else {
		// Leaving combat
		// TODO: Remove combat icon, call combat reset scripts, etc.

		if n.Entity.GetHP() > 0 {
			// TODO: Re-enable action states, stop heroic opportunities
		}
	}
}

// ProcessCombat handles combat processing
func (n *NPC) ProcessCombat() {
	// TODO: Implement combat processing logic
	// This would handle spell casting, AI decisions, etc.
}

// Copy helper methods
func (n *NPC) copySkills(oldNPC *NPC) {
	if oldNPC == nil {
		return
	}

	oldNPC.mutex.RLock()
	oldSkills := make(map[string]*Skill)
	for name, skill := range oldNPC.skills {
		if skill != nil {
			oldSkills[name] = &Skill{
				SkillID:    skill.SkillID,
				Name:       skill.Name,
				CurrentVal: skill.CurrentVal,
				MaxVal:     skill.MaxVal,
			}
		}
	}
	oldNPC.mutex.RUnlock()

	n.SetSkills(oldSkills)
}

func (n *NPC) copySpells(oldNPC *NPC) {
	if oldNPC == nil {
		return
	}

	oldNPC.mutex.RLock()
	oldSpells := make([]*NPCSpell, len(oldNPC.spells))
	for i, spell := range oldNPC.spells {
		if spell != nil {
			oldSpells[i] = spell.Copy()
		}
	}

	// Also copy cast-on spells
	for castType, spells := range oldNPC.castOnSpells {
		for _, spell := range spells {
			if spell != nil {
				oldSpells = append(oldSpells, spell.Copy())
			}
		}
	}
	oldNPC.mutex.RUnlock()

	n.SetSpells(oldSpells)
}

// randomizeAppearance applies appearance randomization
func (n *NPC) randomizeAppearance(flags int32) {
	// TODO: Implement full appearance randomization
	// This is a complex system that would integrate with the appearance system

	// For now, just implement basic randomization
	if n.Entity == nil {
		return
	}

	// Random gender
	if flags&RandomizeGender != 0 {
		gender := int8(rand.Intn(2) + 1) // 1 or 2
		n.Entity.SetGender(gender)
	}

	// Random race (simplified)
	if flags&RandomizeRace != 0 {
		// TODO: Implement race randomization based on alignment
		race := int16(rand.Intn(21)) // 0-20 for basic races
		n.Entity.SetRace(race)
	}

	// Color randomization
	if flags&RandomizeSkinColor != 0 {
		// TODO: Implement skin color randomization
	}

	// More randomization options would be implemented here
}

// Validation methods
func (n *NPC) IsValid() bool {
	if n.Entity == nil {
		return false
	}

	// Basic validation
	if n.Entity.GetLevel() < MinNPCLevel || n.Entity.GetLevel() > MaxNPCLevel {
		return false
	}

	if n.appearanceID < MinAppearanceID || n.appearanceID > MaxAppearanceID {
		return false
	}

	return true
}

// String returns a string representation of the NPC
func (n *NPC) String() string {
	if n.Entity == nil {
		return fmt.Sprintf("NPC{ID: %d, AppearanceID: %d, Entity: nil}", n.npcID, n.appearanceID)
	}

	return fmt.Sprintf("NPC{ID: %d, Name: %s, Level: %d, AppearanceID: %d}",
		n.npcID, n.Entity.GetName(), n.Entity.GetLevel(), n.appearanceID)
}
