package zone

import (
	"fmt"
	"log"
	"sync"
	"time"

	"eq2emu/internal/spawn"
)

// MobMovementManager handles movement for all NPCs and entities in the zone
type MobMovementManager struct {
	zone            *ZoneServer
	movementSpawns  map[int32]*MovementState
	commandQueue    map[int32][]*MovementCommand
	stuckSpawns     map[int32]*StuckInfo
	processedSpawns map[int32]bool
	lastUpdate      time.Time
	isProcessing    bool
	mutex           sync.RWMutex
}

// MovementState tracks the current movement state of a spawn
type MovementState struct {
	SpawnID          int32
	CurrentCommand   *MovementCommand
	CommandQueue     []*MovementCommand
	LastPosition     *Position
	LastMoveTime     time.Time
	IsMoving         bool
	IsStuck          bool
	StuckCount       int
	Speed            float32
	MovementMode     int8
	TargetPosition   *Position
	TargetHeading    float32
	PathNodes        []*PathNode
	CurrentNodeIndex int
	PauseTime        int32
	PauseUntil       time.Time
}

// MovementCommand represents a movement instruction
type MovementCommand struct {
	Type           int8
	TargetX        float32
	TargetY        float32
	TargetZ        float32
	TargetHeading  float32
	Speed          float32
	MovementMode   int8
	StuckBehavior  int8
	MaxDistance    float32
	CompletionFunc func(*spawn.Spawn, bool) // Called when command completes (success bool)
}

// StuckInfo tracks stuck detection for spawns
type StuckInfo struct {
	Position      *Position
	StuckCount    int
	LastStuckTime time.Time
	Behavior      int8
	AttemptCount  int
}

// NewMobMovementManager creates a new movement manager for the zone
func NewMobMovementManager(zone *ZoneServer) *MobMovementManager {
	return &MobMovementManager{
		zone:            zone,
		movementSpawns:  make(map[int32]*MovementState),
		commandQueue:    make(map[int32][]*MovementCommand),
		stuckSpawns:     make(map[int32]*StuckInfo),
		processedSpawns: make(map[int32]bool),
		lastUpdate:      time.Now(),
	}
}

// Process handles movement processing for all managed spawns
func (mm *MobMovementManager) Process() error {
	mm.mutex.Lock()
	defer mm.mutex.Unlock()

	if mm.isProcessing {
		return nil // Already processing
	}

	mm.isProcessing = true
	defer func() { mm.isProcessing = false }()

	now := time.Now()
	deltaTime := now.Sub(mm.lastUpdate).Seconds()
	mm.lastUpdate = now

	// Process each spawn with movement
	for spawnID, state := range mm.movementSpawns {
		spawn := mm.zone.GetSpawn(spawnID)
		if spawn == nil {
			// Spawn no longer exists, remove from tracking
			delete(mm.movementSpawns, spawnID)
			delete(mm.commandQueue, spawnID)
			delete(mm.stuckSpawns, spawnID)
			continue
		}

		// Skip if spawn is paused
		if !state.PauseUntil.IsZero() && now.Before(state.PauseUntil) {
			continue
		}

		// Process current command
		if err := mm.processSpawnMovement(spawn, state, float32(deltaTime)); err != nil {
			log.Printf("%s Error processing movement for spawn %d: %v", LogPrefixMovement, spawnID, err)
		}
	}

	return nil
}

// AddMovementSpawn adds a spawn to movement tracking
func (mm *MobMovementManager) AddMovementSpawn(spawnID int32) {
	mm.mutex.Lock()
	defer mm.mutex.Unlock()

	if _, exists := mm.movementSpawns[spawnID]; exists {
		return // Already tracking
	}

	spawn := mm.zone.GetSpawn(spawnID)
	if spawn == nil {
		return
	}

	x, y, z, heading := spawn.GetPosition()

	mm.movementSpawns[spawnID] = &MovementState{
		SpawnID:      spawnID,
		LastPosition: NewPosition(x, y, z, heading),
		LastMoveTime: time.Now(),
		Speed:        DefaultRunSpeed,
		MovementMode: MovementModeRun,
	}

	mm.commandQueue[spawnID] = make([]*MovementCommand, 0)

	log.Printf("%s Added spawn %d to movement tracking", LogPrefixMovement, spawnID)
}

// RemoveMovementSpawn removes a spawn from movement tracking
func (mm *MobMovementManager) RemoveMovementSpawn(spawnID int32) {
	mm.mutex.Lock()
	defer mm.mutex.Unlock()

	delete(mm.movementSpawns, spawnID)
	delete(mm.commandQueue, spawnID)
	delete(mm.stuckSpawns, spawnID)
	delete(mm.processedSpawns, spawnID)

	log.Printf("%s Removed spawn %d from movement tracking", LogPrefixMovement, spawnID)
}

// MoveTo commands a spawn to move to the specified position
func (mm *MobMovementManager) MoveTo(spawnID int32, x, y, z float32, speed float32) error {
	command := &MovementCommand{
		Type:          MovementCommandMoveTo,
		TargetX:       x,
		TargetY:       y,
		TargetZ:       z,
		Speed:         speed,
		MovementMode:  MovementModeRun,
		StuckBehavior: StuckBehaviorEvade,
	}

	return mm.QueueCommand(spawnID, command)
}

// SwimTo commands a spawn to swim to the specified position
func (mm *MobMovementManager) SwimTo(spawnID int32, x, y, z float32, speed float32) error {
	command := &MovementCommand{
		Type:          MovementCommandSwimTo,
		TargetX:       x,
		TargetY:       y,
		TargetZ:       z,
		Speed:         speed,
		MovementMode:  MovementModeSwim,
		StuckBehavior: StuckBehaviorWarp,
	}

	return mm.QueueCommand(spawnID, command)
}

// TeleportTo instantly moves a spawn to the specified position
func (mm *MobMovementManager) TeleportTo(spawnID int32, x, y, z, heading float32) error {
	command := &MovementCommand{
		Type:          MovementCommandTeleportTo,
		TargetX:       x,
		TargetY:       y,
		TargetZ:       z,
		TargetHeading: heading,
		Speed:         0, // Instant
		MovementMode:  MovementModeRun,
	}

	return mm.QueueCommand(spawnID, command)
}

// RotateTo commands a spawn to rotate to the specified heading
func (mm *MobMovementManager) RotateTo(spawnID int32, heading float32, speed float32) error {
	command := &MovementCommand{
		Type:          MovementCommandRotateTo,
		TargetHeading: heading,
		Speed:         speed,
		MovementMode:  MovementModeRun,
	}

	return mm.QueueCommand(spawnID, command)
}

// StopMoving commands a spawn to stop all movement
func (mm *MobMovementManager) StopMoving(spawnID int32) error {
	command := &MovementCommand{
		Type: MovementCommandStop,
	}

	return mm.QueueCommand(spawnID, command)
}

// EvadeCombat commands a spawn to evade and return to its spawn point
func (mm *MobMovementManager) EvadeCombat(spawnID int32) error {
	spawn := mm.zone.GetSpawn(spawnID)
	if spawn == nil {
		return fmt.Errorf("spawn %d not found", spawnID)
	}

	// Get spawn's original position (would need to be stored somewhere)
	// For now, use current position as placeholder
	x, y, z, heading := spawn.GetPosition()

	command := &MovementCommand{
		Type:          MovementCommandEvadeCombat,
		TargetX:       x,
		TargetY:       y,
		TargetZ:       z,
		TargetHeading: heading,
		Speed:         DefaultRunSpeed * 1.5, // Faster when evading
		MovementMode:  MovementModeRun,
		StuckBehavior: StuckBehaviorWarp,
	}

	return mm.QueueCommand(spawnID, command)
}

// QueueCommand adds a movement command to the spawn's queue
func (mm *MobMovementManager) QueueCommand(spawnID int32, command *MovementCommand) error {
	mm.mutex.Lock()
	defer mm.mutex.Unlock()

	// Ensure spawn is being tracked
	if _, exists := mm.movementSpawns[spawnID]; !exists {
		mm.AddMovementSpawn(spawnID)
	}

	// Add command to queue
	if _, exists := mm.commandQueue[spawnID]; !exists {
		mm.commandQueue[spawnID] = make([]*MovementCommand, 0)
	}

	mm.commandQueue[spawnID] = append(mm.commandQueue[spawnID], command)

	log.Printf("%s Queued movement command %d for spawn %d", LogPrefixMovement, command.Type, spawnID)
	return nil
}

// ClearCommands clears all queued commands for a spawn
func (mm *MobMovementManager) ClearCommands(spawnID int32) {
	mm.mutex.Lock()
	defer mm.mutex.Unlock()

	if queue, exists := mm.commandQueue[spawnID]; exists {
		mm.commandQueue[spawnID] = queue[:0] // Clear slice but keep capacity
	}

	if state, exists := mm.movementSpawns[spawnID]; exists {
		state.CurrentCommand = nil
		state.IsMoving = false
		state.PathNodes = nil
		state.CurrentNodeIndex = 0
	}
}

// IsMoving returns whether a spawn is currently moving
func (mm *MobMovementManager) IsMoving(spawnID int32) bool {
	mm.mutex.RLock()
	defer mm.mutex.RUnlock()

	if state, exists := mm.movementSpawns[spawnID]; exists {
		return state.IsMoving
	}
	return false
}

// GetMovementState returns the current movement state for a spawn
func (mm *MobMovementManager) GetMovementState(spawnID int32) *MovementState {
	mm.mutex.RLock()
	defer mm.mutex.RUnlock()

	if state, exists := mm.movementSpawns[spawnID]; exists {
		// Return a copy to avoid race conditions
		return &MovementState{
			SpawnID:          state.SpawnID,
			IsMoving:         state.IsMoving,
			IsStuck:          state.IsStuck,
			Speed:            state.Speed,
			MovementMode:     state.MovementMode,
			CurrentNodeIndex: state.CurrentNodeIndex,
		}
	}
	return nil
}

// Private methods

func (mm *MobMovementManager) processSpawnMovement(spawn *spawn.Spawn, state *MovementState, deltaTime float32) error {
	// Get next command if not currently executing one
	if state.CurrentCommand == nil {
		state.CurrentCommand = mm.getNextCommand(state.SpawnID)
		if state.CurrentCommand == nil {
			state.IsMoving = false
			return nil // No commands to process
		}
	}

	// Process current command
	completed, err := mm.processMovementCommand(spawn, state, deltaTime)
	if err != nil {
		return err
	}

	// If command completed, call completion function and get next command
	if completed {
		if state.CurrentCommand.CompletionFunc != nil {
			state.CurrentCommand.CompletionFunc(spawn, true)
		}
		state.CurrentCommand = nil
		state.IsMoving = false
	}

	return nil
}

func (mm *MobMovementManager) processMovementCommand(spawn *spawn.Spawn, state *MovementState, deltaTime float32) (bool, error) {
	command := state.CurrentCommand
	if command == nil {
		return true, nil
	}

	switch command.Type {
	case MovementCommandMoveTo:
		return mm.processMoveTo(spawn, state, command, deltaTime)
	case MovementCommandSwimTo:
		return mm.processSwimTo(spawn, state, command, deltaTime)
	case MovementCommandTeleportTo:
		return mm.processTeleportTo(spawn, state, command)
	case MovementCommandRotateTo:
		return mm.processRotateTo(spawn, state, command, deltaTime)
	case MovementCommandStop:
		return mm.processStop(spawn, state, command)
	case MovementCommandEvadeCombat:
		return mm.processEvadeCombat(spawn, state, command, deltaTime)
	default:
		return true, fmt.Errorf("unknown movement command type: %d", command.Type)
	}
}

func (mm *MobMovementManager) processMoveTo(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, deltaTime float32) (bool, error) {
	currentX, currentY, currentZ, currentHeading := spawn.GetPosition()

	// Calculate distance to target
	distanceToTarget := Distance3D(currentX, currentY, currentZ, command.TargetX, command.TargetY, command.TargetZ)

	// Check if we've reached the target
	if distanceToTarget <= 0.5 { // Close enough threshold
		return true, nil
	}

	// Check for stuck condition
	if mm.checkStuck(spawn, state) {
		return mm.handleStuck(spawn, state, command)
	}

	// Calculate movement
	speed := command.Speed
	if speed <= 0 {
		speed = state.Speed
	}

	maxMove := speed * deltaTime
	if maxMove > distanceToTarget {
		maxMove = distanceToTarget
	}

	// Calculate direction
	dx := command.TargetX - currentX
	dy := command.TargetY - currentY
	dz := command.TargetZ - currentZ

	// Normalize direction
	distance := Distance3D(0, 0, 0, dx, dy, dz)
	if distance > 0 {
		dx /= distance
		dy /= distance
		dz /= distance
	}

	// Calculate new position
	newX := currentX + dx*maxMove
	newY := currentY + dy*maxMove
	newZ := currentZ + dz*maxMove

	// Calculate heading to target
	newHeading := CalculateHeading(currentX, currentY, command.TargetX, command.TargetY)

	// Update spawn position
	spawn.SetPosition(newX, newY, newZ, newHeading)

	// Update state
	state.LastPosition.Set(newX, newY, newZ, newHeading)
	state.LastMoveTime = time.Now()
	state.IsMoving = true

	// Mark spawn as changed for client updates
	mm.zone.markSpawnChanged(spawn.GetID())

	return false, nil // Not completed yet
}

func (mm *MobMovementManager) processSwimTo(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, deltaTime float32) (bool, error) {
	// Similar to MoveTo but with different movement mode
	return mm.processMoveTo(spawn, state, command, deltaTime)
}

func (mm *MobMovementManager) processTeleportTo(spawn *spawn.Spawn, state *MovementState, command *MovementCommand) (bool, error) {
	// Instant teleport
	spawn.SetPosition(command.TargetX, command.TargetY, command.TargetZ, command.TargetHeading)

	// Update state
	state.LastPosition.Set(command.TargetX, command.TargetY, command.TargetZ, command.TargetHeading)
	state.LastMoveTime = time.Now()
	state.IsMoving = false

	// Mark spawn as changed
	mm.zone.markSpawnChanged(spawn.GetID())

	log.Printf("%s Teleported spawn %d to (%.2f, %.2f, %.2f)",
		LogPrefixMovement, spawn.GetID(), command.TargetX, command.TargetY, command.TargetZ)

	return true, nil // Completed immediately
}

func (mm *MobMovementManager) processRotateTo(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, deltaTime float32) (bool, error) {
	currentX, currentY, currentZ, currentHeading := spawn.GetPosition()

	// Calculate heading difference
	headingDiff := HeadingDifference(currentHeading, command.TargetHeading)

	// Check if we've reached the target heading
	if abs(headingDiff) <= 1.0 { // Close enough threshold
		spawn.SetPosition(currentX, currentY, currentZ, command.TargetHeading)
		mm.zone.markSpawnChanged(spawn.GetID())
		return true, nil
	}

	// Calculate rotation speed
	rotationSpeed := command.Speed
	if rotationSpeed <= 0 {
		rotationSpeed = 90.0 // Default rotation speed in heading units per second
	}

	maxRotation := rotationSpeed * deltaTime

	// Determine rotation direction and amount
	var rotation float32
	if abs(headingDiff) <= maxRotation {
		rotation = headingDiff
	} else if headingDiff > 0 {
		rotation = maxRotation
	} else {
		rotation = -maxRotation
	}

	// Apply rotation
	newHeading := NormalizeHeading(currentHeading + rotation)
	spawn.SetPosition(currentX, currentY, currentZ, newHeading)

	// Update state
	state.LastPosition.Heading = newHeading
	state.LastMoveTime = time.Now()

	// Mark spawn as changed
	mm.zone.markSpawnChanged(spawn.GetID())

	return false, nil // Not completed yet
}

func (mm *MobMovementManager) processStop(spawn *spawn.Spawn, state *MovementState, command *MovementCommand) (bool, error) {
	state.IsMoving = false
	state.PathNodes = nil
	state.CurrentNodeIndex = 0

	log.Printf("%s Stopped movement for spawn %d", LogPrefixMovement, spawn.GetID())
	return true, nil
}

func (mm *MobMovementManager) processEvadeCombat(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, deltaTime float32) (bool, error) {
	// Similar to MoveTo but with evade behavior
	return mm.processMoveTo(spawn, state, command, deltaTime)
}

func (mm *MobMovementManager) getNextCommand(spawnID int32) *MovementCommand {
	if queue, exists := mm.commandQueue[spawnID]; exists && len(queue) > 0 {
		command := queue[0]
		mm.commandQueue[spawnID] = queue[1:] // Remove first command
		return command
	}
	return nil
}

func (mm *MobMovementManager) checkStuck(spawn *spawn.Spawn, state *MovementState) bool {
	currentX, currentY, currentZ, _ := spawn.GetPosition()

	// Check if spawn has moved significantly since last update
	if state.LastPosition != nil {
		distance := Distance3D(currentX, currentY, currentZ, state.LastPosition.X, state.LastPosition.Y, state.LastPosition.Z)
		if distance < 0.1 && time.Since(state.LastMoveTime) > time.Second*2 {
			// Spawn hasn't moved much in 2 seconds
			return true
		}
	}

	return false
}

func (mm *MobMovementManager) handleStuck(spawn *spawn.Spawn, state *MovementState, command *MovementCommand) (bool, error) {
	spawnID := spawn.GetID()

	// Get or create stuck info
	stuckInfo, exists := mm.stuckSpawns[spawnID]
	if !exists {
		currentX, currentY, currentZ, currentHeading := spawn.GetPosition()
		stuckInfo = &StuckInfo{
			Position:      NewPosition(currentX, currentY, currentZ, currentHeading),
			StuckCount:    0,
			LastStuckTime: time.Now(),
			Behavior:      command.StuckBehavior,
		}
		mm.stuckSpawns[spawnID] = stuckInfo
	}

	stuckInfo.StuckCount++
	stuckInfo.AttemptCount++

	log.Printf("%s Spawn %d is stuck (count: %d)", LogPrefixMovement, spawnID, stuckInfo.StuckCount)

	switch stuckInfo.Behavior {
	case StuckBehaviorNone:
		return true, nil // Give up

	case StuckBehaviorRun:
		// Try to move around the obstacle
		return mm.handleStuckWithRun(spawn, state, command, stuckInfo)

	case StuckBehaviorWarp:
		// Teleport to target
		return mm.handleStuckWithWarp(spawn, state, command, stuckInfo)

	case StuckBehaviorEvade:
		// Return to spawn point
		return mm.handleStuckWithEvade(spawn, state, command, stuckInfo)

	default:
		return true, nil // Unknown behavior, give up
	}
}

func (mm *MobMovementManager) handleStuckWithRun(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, stuckInfo *StuckInfo) (bool, error) {
	if stuckInfo.AttemptCount > 5 {
		return true, nil // Give up after 5 attempts
	}

	// Try a slightly different path
	currentX, currentY, currentZ, _ := spawn.GetPosition()

	// Add some randomness to the movement
	offsetX := float32((time.Now().UnixNano()%100 - 50)) / 50.0 * 2.0
	offsetY := float32((time.Now().UnixNano()%100 - 50)) / 50.0 * 2.0

	newTargetX := command.TargetX + offsetX
	newTargetY := command.TargetY + offsetY

	// Update command with new target
	command.TargetX = newTargetX
	command.TargetY = newTargetY

	log.Printf("%s Trying alternate path for stuck spawn %d", LogPrefixMovement, spawn.GetID())
	return false, nil // Continue with modified command
}

func (mm *MobMovementManager) handleStuckWithWarp(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, stuckInfo *StuckInfo) (bool, error) {
	// Teleport directly to target
	spawn.SetPosition(command.TargetX, command.TargetY, command.TargetZ, command.TargetHeading)
	mm.zone.markSpawnChanged(spawn.GetID())

	log.Printf("%s Warped stuck spawn %d to target", LogPrefixMovement, spawn.GetID())
	return true, nil // Command completed
}

func (mm *MobMovementManager) handleStuckWithEvade(spawn *spawn.Spawn, state *MovementState, command *MovementCommand, stuckInfo *StuckInfo) (bool, error) {
	// Return to original position (evade)
	if stuckInfo.Position != nil {
		spawn.SetPosition(stuckInfo.Position.X, stuckInfo.Position.Y, stuckInfo.Position.Z, stuckInfo.Position.Heading)
		mm.zone.markSpawnChanged(spawn.GetID())

		log.Printf("%s Evaded stuck spawn %d to original position", LogPrefixMovement, spawn.GetID())
	}

	return true, nil // Command completed
}
