package player

import (
	"fmt"
	"sync"
	"time"

	"eq2emu/internal/entity"
)

// Manager handles player management operations
type Manager struct {
	// Players indexed by various keys
	playersLock     sync.RWMutex
	players         map[int32]*Player   // playerID -> Player
	playersByName   map[string]*Player  // name -> Player (case insensitive)
	playersByCharID map[int32]*Player   // characterID -> Player
	playersByZone   map[int32][]*Player // zoneID -> []*Player

	// Player statistics
	stats     PlayerStats
	statsLock sync.RWMutex

	// Event handlers
	eventHandlers []PlayerEventHandler
	eventLock     sync.RWMutex

	// Validators
	validators []PlayerValidator

	// Database interface
	database PlayerDatabase

	// Packet handler
	packetHandler PlayerPacketHandler

	// Notifier
	notifier PlayerNotifier

	// Statistics tracker
	statistics PlayerStatistics

	// Configuration
	config ManagerConfig

	// Shutdown channel
	shutdown chan struct{}

	// Background goroutines
	wg sync.WaitGroup
}

// PlayerStats holds various player statistics
type PlayerStats struct {
	TotalPlayers     int64
	ActivePlayers    int64
	PlayersLoggedIn  int64
	PlayersLoggedOut int64
	AverageLevel     float64
	MaxLevel         int8
	TotalPlayTime    time.Duration
}

// ManagerConfig holds configuration for the player manager
type ManagerConfig struct {
	// Maximum number of players
	MaxPlayers int32

	// Player save interval
	SaveInterval time.Duration

	// Statistics update interval
	StatsInterval time.Duration

	// Enable player validation
	EnableValidation bool

	// Enable event handling
	EnableEvents bool

	// Enable statistics tracking
	EnableStatistics bool
}

// NewManager creates a new player manager
func NewManager(config ManagerConfig) *Manager {
	return &Manager{
		players:         make(map[int32]*Player),
		playersByName:   make(map[string]*Player),
		playersByCharID: make(map[int32]*Player),
		playersByZone:   make(map[int32][]*Player),
		eventHandlers:   make([]PlayerEventHandler, 0),
		validators:      make([]PlayerValidator, 0),
		config:          config,
		shutdown:        make(chan struct{}),
	}
}

// Start starts the player manager
func (m *Manager) Start() error {
	// Start background processes
	if m.config.SaveInterval > 0 {
		m.wg.Add(1)
		go m.savePlayersLoop()
	}

	if m.config.StatsInterval > 0 {
		m.wg.Add(1)
		go m.updateStatsLoop()
	}

	m.wg.Add(1)
	go m.processPlayersLoop()

	return nil
}

// Stop stops the player manager
func (m *Manager) Stop() error {
	close(m.shutdown)
	m.wg.Wait()
	return nil
}

// AddPlayer adds a player to management
func (m *Manager) AddPlayer(player *Player) error {
	if player == nil {
		return fmt.Errorf("player cannot be nil")
	}

	m.playersLock.Lock()
	defer m.playersLock.Unlock()

	// Check if we're at capacity
	if m.config.MaxPlayers > 0 && int32(len(m.players)) >= m.config.MaxPlayers {
		return fmt.Errorf("server at maximum player capacity")
	}

	playerID := player.GetSpawnID()
	characterID := player.GetCharacterID()
	name := player.GetName()
	zoneID := player.GetZone()

	// Check for duplicates
	if _, exists := m.players[playerID]; exists {
		return fmt.Errorf("player with ID %d already exists", playerID)
	}

	if _, exists := m.playersByCharID[characterID]; exists {
		return fmt.Errorf("player with character ID %d already exists", characterID)
	}

	if _, exists := m.playersByName[name]; exists {
		return fmt.Errorf("player with name %s already exists", name)
	}

	// Add to maps
	m.players[playerID] = player
	m.playersByCharID[characterID] = player
	m.playersByName[name] = player

	// Add to zone map
	if m.playersByZone[zoneID] == nil {
		m.playersByZone[zoneID] = make([]*Player, 0)
	}
	m.playersByZone[zoneID] = append(m.playersByZone[zoneID], player)

	// Update statistics
	m.updateStatsForAdd()

	// Fire event
	if m.config.EnableEvents {
		m.firePlayerLoginEvent(player)
	}

	return nil
}

// RemovePlayer removes a player from management
func (m *Manager) RemovePlayer(playerID int32) error {
	m.playersLock.Lock()
	defer m.playersLock.Unlock()

	player, exists := m.players[playerID]
	if !exists {
		return fmt.Errorf("player with ID %d not found", playerID)
	}

	// Remove from maps
	delete(m.players, playerID)
	delete(m.playersByCharID, player.GetCharacterID())
	delete(m.playersByName, player.GetName())

	// Remove from zone map
	zoneID := player.GetZone()
	if zonePlayers, exists := m.playersByZone[zoneID]; exists {
		for i, p := range zonePlayers {
			if p == player {
				m.playersByZone[zoneID] = append(zonePlayers[:i], zonePlayers[i+1:]...)
				break
			}
		}
		// Clean up empty zone lists
		if len(m.playersByZone[zoneID]) == 0 {
			delete(m.playersByZone, zoneID)
		}
	}

	// Update statistics
	m.updateStatsForRemove()

	// Fire event
	if m.config.EnableEvents {
		m.firePlayerLogoutEvent(player)
	}

	// Save player data before removal
	if m.database != nil {
		m.database.SavePlayer(player)
	}

	return nil
}

// GetPlayer returns a player by spawn ID
func (m *Manager) GetPlayer(playerID int32) *Player {
	m.playersLock.RLock()
	defer m.playersLock.RUnlock()

	return m.players[playerID]
}

// GetPlayerByName returns a player by name
func (m *Manager) GetPlayerByName(name string) *Player {
	m.playersLock.RLock()
	defer m.playersLock.RUnlock()

	return m.playersByName[name]
}

// GetPlayerByCharacterID returns a player by character ID
func (m *Manager) GetPlayerByCharacterID(characterID int32) *Player {
	m.playersLock.RLock()
	defer m.playersLock.RUnlock()

	return m.playersByCharID[characterID]
}

// GetAllPlayers returns all managed players
func (m *Manager) GetAllPlayers() []*Player {
	m.playersLock.RLock()
	defer m.playersLock.RUnlock()

	players := make([]*Player, 0, len(m.players))
	for _, player := range m.players {
		players = append(players, player)
	}
	return players
}

// GetPlayersInZone returns all players in a zone
func (m *Manager) GetPlayersInZone(zoneID int32) []*Player {
	m.playersLock.RLock()
	defer m.playersLock.RUnlock()

	if zonePlayers, exists := m.playersByZone[zoneID]; exists {
		// Return a copy to avoid race conditions
		players := make([]*Player, len(zonePlayers))
		copy(players, zonePlayers)
		return players
	}

	return []*Player{}
}

// SendToAll sends a message to all players
func (m *Manager) SendToAll(message interface{}) error {
	if m.packetHandler == nil {
		return fmt.Errorf("no packet handler configured")
	}

	players := m.GetAllPlayers()
	return m.packetHandler.BroadcastPacket(players, message)
}

// SendToZone sends a message to all players in a zone
func (m *Manager) SendToZone(zoneID int32, message interface{}) error {
	if m.packetHandler == nil {
		return fmt.Errorf("no packet handler configured")
	}

	players := m.GetPlayersInZone(zoneID)
	return m.packetHandler.BroadcastPacket(players, message)
}

// MovePlayerToZone moves a player to a different zone
func (m *Manager) MovePlayerToZone(playerID, newZoneID int32) error {
	m.playersLock.Lock()
	defer m.playersLock.Unlock()

	player, exists := m.players[playerID]
	if !exists {
		return fmt.Errorf("player with ID %d not found", playerID)
	}

	oldZoneID := player.GetZone()
	if oldZoneID == newZoneID {
		return nil // Already in the zone
	}

	// Remove from old zone
	if zonePlayers, exists := m.playersByZone[oldZoneID]; exists {
		for i, p := range zonePlayers {
			if p == player {
				m.playersByZone[oldZoneID] = append(zonePlayers[:i], zonePlayers[i+1:]...)
				break
			}
		}
		if len(m.playersByZone[oldZoneID]) == 0 {
			delete(m.playersByZone, oldZoneID)
		}
	}

	// Add to new zone
	if m.playersByZone[newZoneID] == nil {
		m.playersByZone[newZoneID] = make([]*Player, 0)
	}
	m.playersByZone[newZoneID] = append(m.playersByZone[newZoneID], player)

	// Update player's zone
	player.SetZone(newZoneID)

	// Fire event
	if m.config.EnableEvents {
		m.firePlayerZoneChangeEvent(player, oldZoneID, newZoneID)
	}

	return nil
}

// GetPlayerCount returns the current number of players
func (m *Manager) GetPlayerCount() int32 {
	m.playersLock.RLock()
	defer m.playersLock.RUnlock()

	return int32(len(m.players))
}

// GetZonePlayerCount returns the number of players in a zone
func (m *Manager) GetZonePlayerCount(zoneID int32) int32 {
	m.playersLock.RLock()
	defer m.playersLock.RUnlock()

	if zonePlayers, exists := m.playersByZone[zoneID]; exists {
		return int32(len(zonePlayers))
	}
	return 0
}

// GetPlayerStats returns current player statistics
func (m *Manager) GetPlayerStats() PlayerStats {
	m.statsLock.RLock()
	defer m.statsLock.RUnlock()

	return m.stats
}

// AddEventHandler adds an event handler
func (m *Manager) AddEventHandler(handler PlayerEventHandler) {
	m.eventLock.Lock()
	defer m.eventLock.Unlock()

	m.eventHandlers = append(m.eventHandlers, handler)
}

// AddValidator adds a validator
func (m *Manager) AddValidator(validator PlayerValidator) {
	m.validators = append(m.validators, validator)
}

// SetDatabase sets the database interface
func (m *Manager) SetDatabase(db PlayerDatabase) {
	m.database = db
}

// SetPacketHandler sets the packet handler
func (m *Manager) SetPacketHandler(handler PlayerPacketHandler) {
	m.packetHandler = handler
}

// SetNotifier sets the notifier
func (m *Manager) SetNotifier(notifier PlayerNotifier) {
	m.notifier = notifier
}

// SetStatistics sets the statistics tracker
func (m *Manager) SetStatistics(stats PlayerStatistics) {
	m.statistics = stats
}

// ValidatePlayer validates a player using all validators
func (m *Manager) ValidatePlayer(player *Player) error {
	if !m.config.EnableValidation {
		return nil
	}

	for _, validator := range m.validators {
		if err := validator.ValidateLogin(player); err != nil {
			return err
		}
	}
	return nil
}

// savePlayersLoop periodically saves all players
func (m *Manager) savePlayersLoop() {
	defer m.wg.Done()

	ticker := time.NewTicker(m.config.SaveInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			m.saveAllPlayers()
		case <-m.shutdown:
			// Final save before shutdown
			m.saveAllPlayers()
			return
		}
	}
}

// updateStatsLoop periodically updates statistics
func (m *Manager) updateStatsLoop() {
	defer m.wg.Done()

	ticker := time.NewTicker(m.config.StatsInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			m.updatePlayerStats()
		case <-m.shutdown:
			return
		}
	}
}

// processPlayersLoop processes player updates
func (m *Manager) processPlayersLoop() {
	defer m.wg.Done()

	ticker := time.NewTicker(100 * time.Millisecond) // 10Hz
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			m.processAllPlayers()
		case <-m.shutdown:
			return
		}
	}
}

// saveAllPlayers saves all players to database
func (m *Manager) saveAllPlayers() {
	if m.database == nil {
		return
	}

	players := m.GetAllPlayers()
	for _, player := range players {
		m.database.SavePlayer(player)
	}
}

// updatePlayerStats updates player statistics
func (m *Manager) updatePlayerStats() {
	m.playersLock.RLock()
	defer m.playersLock.RUnlock()

	m.statsLock.Lock()
	defer m.statsLock.Unlock()

	m.stats.ActivePlayers = int64(len(m.players))

	var totalLevel int64
	var maxLevel int8

	for _, player := range m.players {
		level := player.GetLevel()
		totalLevel += int64(level)
		if level > maxLevel {
			maxLevel = level
		}
	}

	if len(m.players) > 0 {
		m.stats.AverageLevel = float64(totalLevel) / float64(len(m.players))
	}
	m.stats.MaxLevel = maxLevel
}

// processAllPlayers processes updates for all players
func (m *Manager) processAllPlayers() {
	players := m.GetAllPlayers()

	for _, player := range players {
		// Process spawn state queue
		player.CheckSpawnStateQueue()

		// Process combat
		player.ProcessCombat()

		// Process range updates
		player.ProcessSpawnRangeUpdates()

		// TODO: Add other periodic processing
	}
}

// updateStatsForAdd updates stats when a player is added
func (m *Manager) updateStatsForAdd() {
	m.statsLock.Lock()
	defer m.statsLock.Unlock()

	m.stats.TotalPlayers++
	m.stats.PlayersLoggedIn++
}

// updateStatsForRemove updates stats when a player is removed
func (m *Manager) updateStatsForRemove() {
	m.statsLock.Lock()
	defer m.statsLock.Unlock()

	m.stats.PlayersLoggedOut++
}

// Event firing methods
func (m *Manager) firePlayerLoginEvent(player *Player) {
	m.eventLock.RLock()
	defer m.eventLock.RUnlock()

	for _, handler := range m.eventHandlers {
		handler.OnPlayerLogin(player)
	}

	if m.statistics != nil {
		m.statistics.RecordPlayerLogin(player)
	}
}

func (m *Manager) firePlayerLogoutEvent(player *Player) {
	m.eventLock.RLock()
	defer m.eventLock.RUnlock()

	for _, handler := range m.eventHandlers {
		handler.OnPlayerLogout(player)
	}

	if m.statistics != nil {
		m.statistics.RecordPlayerLogout(player)
	}
}

func (m *Manager) firePlayerZoneChangeEvent(player *Player, fromZoneID, toZoneID int32) {
	m.eventLock.RLock()
	defer m.eventLock.RUnlock()

	for _, handler := range m.eventHandlers {
		handler.OnPlayerZoneChange(player, fromZoneID, toZoneID)
	}
}

// FirePlayerLevelUpEvent fires a level up event
func (m *Manager) FirePlayerLevelUpEvent(player *Player, newLevel int8) {
	m.eventLock.RLock()
	defer m.eventLock.RUnlock()

	for _, handler := range m.eventHandlers {
		handler.OnPlayerLevelUp(player, newLevel)
	}

	if m.notifier != nil {
		m.notifier.NotifyLevelUp(player, newLevel)
	}
}

// FirePlayerDeathEvent fires a death event
func (m *Manager) FirePlayerDeathEvent(player *Player, killer entity.Entity) {
	m.eventLock.RLock()
	defer m.eventLock.RUnlock()

	for _, handler := range m.eventHandlers {
		handler.OnPlayerDeath(player, killer)
	}

	if m.statistics != nil {
		m.statistics.RecordPlayerDeath(player, killer)
	}
}

// FirePlayerResurrectEvent fires a resurrect event
func (m *Manager) FirePlayerResurrectEvent(player *Player) {
	m.eventLock.RLock()
	defer m.eventLock.RUnlock()

	for _, handler := range m.eventHandlers {
		handler.OnPlayerResurrect(player)
	}
}
