package pathfinder

import (
	"fmt"
	"log"
	"math"
	"sync/atomic"
	"time"
)

// NewPathfinderManager creates a new pathfinder manager for a zone
func NewPathfinderManager(zoneName string) *PathfinderManager {
	pm := &PathfinderManager{
		zoneName: zoneName,
		enabled:  false,
	}
	
	// Always create a null pathfinder as fallback
	pm.fallback = NewNullPathfinder()
	pm.backend = pm.fallback
	
	return pm
}

// SetBackend sets the pathfinding backend
func (pm *PathfinderManager) SetBackend(backend PathfindingBackend) error {
	pm.mutex.Lock()
	defer pm.mutex.Unlock()
	
	if backend == nil {
		return fmt.Errorf("pathfinding backend cannot be nil")
	}
	
	pm.backend = backend
	pm.enabled = backend.IsLoaded()
	
	log.Printf("[Pathfinder] Set backend '%s' for zone '%s' (enabled: %t)", 
		backend.GetName(), pm.zoneName, pm.enabled)
	
	return nil
}

// SetEnabled enables or disables pathfinding
func (pm *PathfinderManager) SetEnabled(enabled bool) {
	pm.mutex.Lock()
	defer pm.mutex.Unlock()
	
	pm.enabled = enabled && pm.backend.IsLoaded()
	
	log.Printf("[Pathfinder] Pathfinding %s for zone '%s'", 
		map[bool]string{true: "enabled", false: "disabled"}[pm.enabled], pm.zoneName)
}

// IsEnabled returns whether pathfinding is enabled
func (pm *PathfinderManager) IsEnabled() bool {
	pm.mutex.RLock()
	defer pm.mutex.RUnlock()
	return pm.enabled
}

// FindRoute finds a route between two points with basic options
func (pm *PathfinderManager) FindRoute(start, end [3]float32, flags PathingPolyFlags) *PathfindingResult {
	startTime := time.Now()
	defer func() {
		atomic.AddInt64(&pm.pathRequests, 1)
		duration := time.Since(startTime)
		pm.updateAveragePathTime(float64(duration.Nanoseconds()) / 1e6) // Convert to milliseconds
	}()
	
	pm.mutex.RLock()
	backend := pm.backend
	enabled := pm.enabled
	pm.mutex.RUnlock()
	
	// Use fallback if pathfinding is disabled
	if !enabled {
		backend = pm.fallback
	}
	
	result := backend.FindRoute(start, end, flags)
	pm.updateStats(result)
	
	return result
}

// FindPath finds a path between two points with advanced options
func (pm *PathfinderManager) FindPath(start, end [3]float32, options *PathfinderOptions) *PathfindingResult {
	startTime := time.Now()
	defer func() {
		atomic.AddInt64(&pm.pathRequests, 1)
		duration := time.Since(startTime)
		pm.updateAveragePathTime(float64(duration.Nanoseconds()) / 1e6) // Convert to milliseconds
	}()
	
	// Use default options if none provided
	if options == nil {
		options = GetDefaultPathfinderOptions()
	}
	
	pm.mutex.RLock()
	backend := pm.backend
	enabled := pm.enabled
	pm.mutex.RUnlock()
	
	// Use fallback if pathfinding is disabled
	if !enabled {
		backend = pm.fallback
	}
	
	result := backend.FindPath(start, end, options)
	pm.updateStats(result)
	
	return result
}

// GetRandomLocation returns a random walkable location near the start point
func (pm *PathfinderManager) GetRandomLocation(start [3]float32) [3]float32 {
	pm.mutex.RLock()
	backend := pm.backend
	enabled := pm.enabled
	pm.mutex.RUnlock()
	
	// Use fallback if pathfinding is disabled
	if !enabled {
		backend = pm.fallback
	}
	
	return backend.GetRandomLocation(start)
}

// GetStats returns current pathfinding statistics
func (pm *PathfinderManager) GetStats() *PathfindingStats {
	pm.mutex.RLock()
	defer pm.mutex.RUnlock()
	
	totalRequests := atomic.LoadInt64(&pm.pathRequests)
	successfulPaths := atomic.LoadInt64(&pm.successfulPaths)
	partialPaths := atomic.LoadInt64(&pm.partialPaths)
	failedPaths := atomic.LoadInt64(&pm.failedPaths)
	
	var successRate float64
	if totalRequests > 0 {
		successRate = float64(successfulPaths) / float64(totalRequests) * 100.0
	}
	
	return &PathfindingStats{
		TotalRequests:   totalRequests,
		SuccessfulPaths: successfulPaths,
		PartialPaths:    partialPaths,
		FailedPaths:     failedPaths,
		SuccessRate:     successRate,
		AveragePathTime: pm.averagePathTime,
		BackendName:     pm.backend.GetName(),
		IsEnabled:       pm.enabled,
	}
}

// ResetStats resets all pathfinding statistics
func (pm *PathfinderManager) ResetStats() {
	atomic.StoreInt64(&pm.pathRequests, 0)
	atomic.StoreInt64(&pm.successfulPaths, 0)
	atomic.StoreInt64(&pm.partialPaths, 0)
	atomic.StoreInt64(&pm.failedPaths, 0)
	
	pm.mutex.Lock()
	pm.averagePathTime = 0.0
	pm.mutex.Unlock()
}

// GetZoneName returns the zone name for this pathfinder
func (pm *PathfinderManager) GetZoneName() string {
	return pm.zoneName
}

// GetBackendName returns the name of the current pathfinding backend
func (pm *PathfinderManager) GetBackendName() string {
	pm.mutex.RLock()
	defer pm.mutex.RUnlock()
	return pm.backend.GetName()
}

// Private methods

func (pm *PathfinderManager) updateStats(result *PathfindingResult) {
	if result == nil {
		atomic.AddInt64(&pm.failedPaths, 1)
		return
	}
	
	if result.Path != nil && !result.Partial {
		atomic.AddInt64(&pm.successfulPaths, 1)
	} else if result.Partial {
		atomic.AddInt64(&pm.partialPaths, 1)
	} else {
		atomic.AddInt64(&pm.failedPaths, 1)
	}
}

func (pm *PathfinderManager) updateAveragePathTime(timeMs float64) {
	pm.mutex.Lock()
	defer pm.mutex.Unlock()
	
	// Simple moving average (this could be improved with more sophisticated averaging)
	if pm.averagePathTime == 0.0 {
		pm.averagePathTime = timeMs
	} else {
		pm.averagePathTime = (pm.averagePathTime*0.9 + timeMs*0.1)
	}
}

// GetDefaultPathfinderOptions returns default pathfinding options
func GetDefaultPathfinderOptions() *PathfinderOptions {
	return &PathfinderOptions{
		Flags:      PathingNotDisabled,
		SmoothPath: true,
		StepSize:   DefaultStepSize,
		FlagCost: [10]float32{
			DefaultFlagCost0, DefaultFlagCost1, DefaultFlagCost2, DefaultFlagCost3, DefaultFlagCost4,
			DefaultFlagCost5, DefaultFlagCost6, DefaultFlagCost7, DefaultFlagCost8, DefaultFlagCost9,
		},
		Offset: DefaultOffset,
	}
}

// ValidatePath validates a generated path for basic correctness
func ValidatePath(path *Path) error {
	if path == nil {
		return fmt.Errorf("path is nil")
	}
	
	if len(path.Nodes) == 0 {
		return fmt.Errorf("path has no nodes")
	}
	
	if len(path.Nodes) > MaxPathNodes {
		return fmt.Errorf("path has too many nodes: %d > %d", len(path.Nodes), MaxPathNodes)
	}
	
	// Check for reasonable distances between nodes
	totalDistance := float32(0.0)
	for i := 1; i < len(path.Nodes); i++ {
		prev := path.Nodes[i-1].Position
		curr := path.Nodes[i].Position
		
		dx := curr[0] - prev[0]
		dy := curr[1] - prev[1]
		dz := curr[2] - prev[2]
		distance := float32(math.Sqrt(float64(dx*dx + dy*dy + dz*dz)))
		
		if distance > MaxPathNodeDistance {
			return fmt.Errorf("path node %d is too far from previous node: %.2f > %.2f", 
				i, distance, MaxPathNodeDistance)
		}
		
		totalDistance += distance
	}
	
	if totalDistance < MinPathDistance {
		return fmt.Errorf("path is too short: %.2f < %.2f", totalDistance, MinPathDistance)
	}
	
	if totalDistance > MaxPathDistance {
		return fmt.Errorf("path is too long: %.2f > %.2f", totalDistance, MaxPathDistance)
	}
	
	path.Distance = totalDistance
	return nil
}