package region

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

// NewRegionManager creates a new region manager for a zone
func NewRegionManager(zoneName string) *RegionManager {
	return &RegionManager{
		zoneName:      zoneName,
		regionMaps:    NewRegionMapRange(zoneName),
		activeNodes:   make(map[string]*RegionNode),
		playerRegions: make(map[int32]map[string]bool),
	}
}

// AddVersionRange adds a version-specific region map
func (rm *RegionManager) AddVersionRange(minVersion, maxVersion int32, regionMap RegionMap) error {
	return rm.regionMaps.AddVersionRange(minVersion, maxVersion, regionMap)
}

// GetRegionMap returns the appropriate region map for a client version
func (rm *RegionManager) GetRegionMap(version int32) RegionMap {
	return rm.regionMaps.FindRegionByVersion(version)
}

// ReturnRegionType returns the region type at the given location for a client version
func (rm *RegionManager) ReturnRegionType(location [3]float32, gridID int32, version int32) WaterRegionType {
	startTime := time.Now()
	defer func() {
		atomic.AddInt64(&rm.regionChecks, 1)
		// Update average check time (simplified moving average)
		// This would be implemented in a more sophisticated stats system
	}()
	
	regionMap := rm.GetRegionMap(version)
	if regionMap == nil {
		return RegionTypeNormal
	}
	
	return regionMap.ReturnRegionType(location, gridID)
}

// InWater checks if the location is in water for a client version
func (rm *RegionManager) InWater(location [3]float32, gridID int32, version int32) bool {
	regionMap := rm.GetRegionMap(version)
	if regionMap == nil {
		return false
	}
	
	return regionMap.InWater(location, gridID)
}

// InLava checks if the location is in lava for a client version
func (rm *RegionManager) InLava(location [3]float32, gridID int32, version int32) bool {
	regionMap := rm.GetRegionMap(version)
	if regionMap == nil {
		return false
	}
	
	return regionMap.InLava(location, gridID)
}

// InLiquid checks if the location is in any liquid for a client version
func (rm *RegionManager) InLiquid(location [3]float32, version int32) bool {
	regionMap := rm.GetRegionMap(version)
	if regionMap == nil {
		return false
	}
	
	return regionMap.InLiquid(location)
}

// InPvP checks if the location is in a PvP area for a client version
func (rm *RegionManager) InPvP(location [3]float32, version int32) bool {
	regionMap := rm.GetRegionMap(version)
	if regionMap == nil {
		return false
	}
	
	return regionMap.InPvP(location)
}

// InZoneLine checks if the location is in a zone line for a client version
func (rm *RegionManager) InZoneLine(location [3]float32, version int32) bool {
	regionMap := rm.GetRegionMap(version)
	if regionMap == nil {
		return false
	}
	
	return regionMap.InZoneLine(location)
}

// AddRegionNode adds a new region node
func (rm *RegionManager) AddRegionNode(name, envName string, gridID, triggerWidgetID uint32, distance float32, position [3]float32) error {
	rm.mutex.Lock()
	defer rm.mutex.Unlock()
	
	if _, exists := rm.activeNodes[name]; exists {
		return fmt.Errorf("region node '%s' already exists", name)
	}
	
	node := &RegionNode{
		Name:            name,
		EnvironmentName: envName,
		GridID:          gridID,
		TriggerWidgetID: triggerWidgetID,
		Distance:        distance,
		Position:        position,
		Active:          true,
		PlayerList:      make(map[int32]bool),
	}
	
	rm.activeNodes[name] = node
	atomic.AddInt32(&rm.activeRegions, 1)
	
	log.Printf("[Region] Added region node '%s' at position (%.2f, %.2f, %.2f) in zone '%s'",
		name, position[0], position[1], position[2], rm.zoneName)
	
	return nil
}

// RemoveRegionNode removes a region node by name
func (rm *RegionManager) RemoveRegionNode(name string) error {
	rm.mutex.Lock()
	defer rm.mutex.Unlock()
	
	node, exists := rm.activeNodes[name]
	if !exists {
		return fmt.Errorf("region node '%s' not found", name)
	}
	
	// Remove players from this region
	node.mutex.Lock()
	for playerID := range node.PlayerList {
		rm.removePlayerFromRegion(playerID, name)
	}
	node.mutex.Unlock()
	
	delete(rm.activeNodes, name)
	atomic.AddInt32(&rm.activeRegions, -1)
	
	log.Printf("[Region] Removed region node '%s' from zone '%s'", name, rm.zoneName)
	
	return nil
}

// UpdatePlayerRegions updates the regions for a player based on their position
func (rm *RegionManager) UpdatePlayerRegions(playerID int32, position [3]float32, version int32) {
	rm.mutex.RLock()
	currentRegions := make(map[string]bool)
	if playerRegions, exists := rm.playerRegions[playerID]; exists {
		for regionName := range playerRegions {
			currentRegions[regionName] = true
		}
	}
	rm.mutex.RUnlock()
	
	newRegions := make(map[string]bool)
	
	// Check all active region nodes
	rm.mutex.RLock()
	for regionName, node := range rm.activeNodes {
		if !node.Active {
			continue
		}
		
		// Calculate distance to region
		dx := position[0] - node.Position[0]
		dy := position[1] - node.Position[1]
		dz := position[2] - node.Position[2]
		distance := float32(math.Sqrt(float64(dx*dx + dy*dy + dz*dz)))
		
		// Check if player is within region
		if distance <= node.Distance {
			newRegions[regionName] = true
			
			// Add player to region if not already there
			if !currentRegions[regionName] {
				rm.addPlayerToRegion(playerID, regionName, position)
			}
		}
	}
	rm.mutex.RUnlock()
	
	// Remove player from regions they've left
	for regionName := range currentRegions {
		if !newRegions[regionName] {
			rm.removePlayerFromRegion(playerID, regionName)
		}
	}
	
	// Update player's region list
	rm.mutex.Lock()
	if len(newRegions) > 0 {
		rm.playerRegions[playerID] = newRegions
	} else {
		delete(rm.playerRegions, playerID)
	}
	rm.mutex.Unlock()
}

// RemovePlayer removes a player from all regions
func (rm *RegionManager) RemovePlayer(playerID int32) {
	rm.mutex.Lock()
	defer rm.mutex.Unlock()
	
	if playerRegions, exists := rm.playerRegions[playerID]; exists {
		for regionName := range playerRegions {
			if node, nodeExists := rm.activeNodes[regionName]; nodeExists {
				node.mutex.Lock()
				delete(node.PlayerList, playerID)
				node.mutex.Unlock()
			}
		}
		delete(rm.playerRegions, playerID)
	}
}

// GetActiveRegions returns a list of active region names
func (rm *RegionManager) GetActiveRegions() []string {
	rm.mutex.RLock()
	defer rm.mutex.RUnlock()
	
	regions := make([]string, 0, len(rm.activeNodes))
	for name, node := range rm.activeNodes {
		if node.Active {
			regions = append(regions, name)
		}
	}
	
	return regions
}

// GetPlayersInRegion returns a list of player IDs in the specified region
func (rm *RegionManager) GetPlayersInRegion(regionName string) []int32 {
	rm.mutex.RLock()
	node, exists := rm.activeNodes[regionName]
	rm.mutex.RUnlock()
	
	if !exists {
		return nil
	}
	
	node.mutex.RLock()
	defer node.mutex.RUnlock()
	
	players := make([]int32, 0, len(node.PlayerList))
	for playerID := range node.PlayerList {
		players = append(players, playerID)
	}
	
	return players
}

// GetPlayerRegions returns the regions a player is currently in
func (rm *RegionManager) GetPlayerRegions(playerID int32) []string {
	rm.mutex.RLock()
	defer rm.mutex.RUnlock()
	
	if playerRegions, exists := rm.playerRegions[playerID]; exists {
		regions := make([]string, 0, len(playerRegions))
		for regionName := range playerRegions {
			regions = append(regions, regionName)
		}
		return regions
	}
	
	return nil
}

// GetStats returns current region statistics
func (rm *RegionManager) GetStats() *RegionStats {
	rm.mutex.RLock()
	defer rm.mutex.RUnlock()
	
	return &RegionStats{
		TotalRegionChecks:     atomic.LoadInt64(&rm.regionChecks),
		TotalRegionTransitions: atomic.LoadInt64(&rm.regionTransitions),
		ActiveRegions:         atomic.LoadInt32(&rm.activeRegions),
		ActivePlayers:         int32(len(rm.playerRegions)),
		RegionMapsLoaded:      int32(rm.regionMaps.GetLoadedMapCount()),
		AverageCheckTime:      0.0, // Would be calculated from timing data
	}
}

// ResetStats resets all region statistics
func (rm *RegionManager) ResetStats() {
	atomic.StoreInt64(&rm.regionChecks, 0)
	atomic.StoreInt64(&rm.regionTransitions, 0)
}

// GetZoneName returns the zone name for this region manager
func (rm *RegionManager) GetZoneName() string {
	return rm.zoneName
}

// Private methods

func (rm *RegionManager) addPlayerToRegion(playerID int32, regionName string, position [3]float32) {
	rm.mutex.RLock()
	node, exists := rm.activeNodes[regionName]
	rm.mutex.RUnlock()
	
	if exists {
		node.mutex.Lock()
		node.PlayerList[playerID] = true
		node.mutex.Unlock()
		
		atomic.AddInt64(&rm.regionTransitions, 1)
		
		log.Printf("[Region] Player %d entered region '%s' at position (%.2f, %.2f, %.2f)",
			playerID, regionName, position[0], position[1], position[2])
	}
}

func (rm *RegionManager) removePlayerFromRegion(playerID int32, regionName string) {
	rm.mutex.RLock()
	node, exists := rm.activeNodes[regionName]
	rm.mutex.RUnlock()
	
	if exists {
		node.mutex.Lock()
		delete(node.PlayerList, playerID)
		node.mutex.Unlock()
		
		atomic.AddInt64(&rm.regionTransitions, 1)
		
		log.Printf("[Region] Player %d left region '%s'", playerID, regionName)
	}
}