package alt_advancement

import (
	"fmt"
	"time"
)

// NewAAManager creates a new AA manager
func NewAAManager(config AAManagerConfig) *AAManager {
	return &AAManager{
		masterAAList:   NewMasterAAList(),
		masterNodeList: NewMasterAANodeList(),
		playerStates:   make(map[int32]*AAPlayerState),
		config:         config,
		eventHandlers:  make([]AAEventHandler, 0),
		stats:          AAManagerStats{},
		stopChan:       make(chan struct{}),
	}
}

// Start starts the AA manager
func (am *AAManager) Start() error {
	// Load AA data
	if err := am.LoadAAData(); err != nil {
		return fmt.Errorf("failed to load AA data: %v", err)
	}

	// Start background processes
	if am.config.UpdateInterval > 0 {
		am.wg.Add(1)
		go am.updateStatsLoop()
	}

	if am.config.AutoSave && am.config.SaveInterval > 0 {
		am.wg.Add(1)
		go am.autoSaveLoop()
	}

	return nil
}

// Stop stops the AA manager
func (am *AAManager) Stop() error {
	close(am.stopChan)
	am.wg.Wait()

	// Save all player states if auto-save is enabled
	if am.config.AutoSave {
		am.saveAllPlayerStates()
	}

	return nil
}

// IsRunning returns true if the manager is running
func (am *AAManager) IsRunning() bool {
	select {
	case <-am.stopChan:
		return false
	default:
		return true
	}
}

// LoadAAData loads all AA data from the database
func (am *AAManager) LoadAAData() error {
	if am.database == nil {
		return fmt.Errorf("database not configured")
	}

	startTime := time.Now()

	// Load AA definitions
	if err := am.database.LoadAltAdvancements(); err != nil {
		return fmt.Errorf("failed to load AAs: %v", err)
	}

	// Load tree nodes
	if err := am.database.LoadTreeNodes(); err != nil {
		return fmt.Errorf("failed to load tree nodes: %v", err)
	}

	// Update statistics
	am.statsMutex.Lock()
	am.stats.TotalAAsLoaded = int64(am.masterAAList.Size())
	am.stats.TotalNodesLoaded = int64(am.masterNodeList.Size())
	am.stats.LastLoadTime = startTime
	am.stats.LoadDuration = time.Since(startTime)
	am.statsMutex.Unlock()

	// Fire load event
	am.fireSystemLoadedEvent(int32(am.stats.TotalAAsLoaded), int32(am.stats.TotalNodesLoaded))

	return nil
}

// ReloadAAData reloads all AA data
func (am *AAManager) ReloadAAData() error {
	// Clear existing data
	am.masterAAList.DestroyAltAdvancements()
	am.masterNodeList.DestroyTreeNodes()

	// Clear cached player states
	am.statesMutex.Lock()
	am.playerStates = make(map[int32]*AAPlayerState)
	am.statesMutex.Unlock()

	// Reload data
	err := am.LoadAAData()
	if err == nil {
		am.fireDataReloadedEvent()
	}

	return err
}

// LoadPlayerAA loads AA data for a specific player
func (am *AAManager) LoadPlayerAA(characterID int32) (*AAPlayerState, error) {
	// For backwards compatibility, delegate to GetPlayerAAState
	// Note: GetPlayerAAState already handles thread safety
	return am.GetPlayerAAState(characterID)
}

// loadPlayerAAFromDatabase loads player data directly from database (internal helper)
func (am *AAManager) loadPlayerAAFromDatabase(characterID int32) (*AAPlayerState, error) {
	if am.database == nil {
		return nil, fmt.Errorf("database not configured")
	}

	playerState, err := am.database.LoadPlayerAA(characterID)
	if err != nil {
		return nil, fmt.Errorf("failed to load player AA: %v", err)
	}

	return playerState, nil
}

// SavePlayerAA saves AA data for a specific player
func (am *AAManager) SavePlayerAA(characterID int32) error {
	if am.database == nil {
		return fmt.Errorf("database not configured")
	}

	// Get player state
	playerState := am.getPlayerState(characterID)
	if playerState == nil {
		return fmt.Errorf("player state not found for character %d", characterID)
	}

	// Save to database
	return am.database.SavePlayerAA(playerState)
}

// GetPlayerAAState returns the AA state for a player
func (am *AAManager) GetPlayerAAState(characterID int32) (*AAPlayerState, error) {
	// Try to get from cache first (read lock)
	am.statesMutex.RLock()
	if playerState, exists := am.playerStates[characterID]; exists {
		am.statesMutex.RUnlock()
		return playerState, nil
	}
	am.statesMutex.RUnlock()

	// Need to load from database, use write lock to prevent race condition
	am.statesMutex.Lock()
	defer am.statesMutex.Unlock()
	
	// Double-check pattern: another goroutine might have loaded it while we waited
	if playerState, exists := am.playerStates[characterID]; exists {
		return playerState, nil
	}

	// Load from database if not cached
	playerState, err := am.loadPlayerAAFromDatabase(characterID)
	if err != nil {
		return nil, err
	}

	// Cache the player state (already have write lock)
	am.playerStates[characterID] = playerState

	// Fire load event
	am.firePlayerAALoadedEvent(characterID, playerState)

	return playerState, nil
}

// PurchaseAA purchases an AA for a player
func (am *AAManager) PurchaseAA(characterID int32, nodeID int32, targetRank int8) error {
	// Get player state - this handles loading if needed
	playerState, err := am.GetPlayerAAState(characterID)
	if err != nil {
		return fmt.Errorf("failed to get player state: %v", err)
	}

	// Get AA data
	aaData := am.masterAAList.GetAltAdvancementByNodeID(nodeID)
	if aaData == nil {
		return fmt.Errorf("AA node %d not found", nodeID)
	}

	// Validate purchase
	if am.validator != nil {
		if err := am.validator.ValidateAAPurchase(playerState, nodeID, targetRank); err != nil {
			am.updateErrorStats("validation_errors")
			return fmt.Errorf("validation failed: %v", err)
		}
	}

	// Perform purchase
	err = am.performAAPurchase(playerState, aaData, targetRank)
	if err != nil {
		return err
	}

	// Fire purchase event
	pointsSpent := int32(aaData.RankCost) * int32(targetRank)
	am.fireAAPurchasedEvent(characterID, nodeID, targetRank, pointsSpent)

	// Send notification
	if am.notifier != nil {
		am.notifier.NotifyAAPurchaseSuccess(characterID, aaData.Name, targetRank)
	}

	// Update statistics
	if am.statistics != nil {
		am.statistics.RecordAAPurchase(characterID, nodeID, pointsSpent)
	}

	return nil
}

// RefundAA refunds an AA for a player
func (am *AAManager) RefundAA(characterID int32, nodeID int32) error {
	// Get player state
	playerState := am.getPlayerState(characterID)
	if playerState == nil {
		return fmt.Errorf("player state not found")
	}

	// Get AA data
	aaData := am.masterAAList.GetAltAdvancementByNodeID(nodeID)
	if aaData == nil {
		return fmt.Errorf("AA node %d not found", nodeID)
	}

	// Get current progress
	progress, exists := playerState.AAProgress[nodeID]
	if !exists || progress.CurrentRank == 0 {
		return fmt.Errorf("AA not purchased or already at rank 0")
	}

	// Calculate refund amount
	pointsRefunded := progress.PointsSpent

	// Perform refund
	playerState.mutex.Lock()
	delete(playerState.AAProgress, nodeID)
	playerState.SpentPoints -= pointsRefunded
	playerState.AvailablePoints += pointsRefunded
	playerState.needsSync = true
	playerState.mutex.Unlock()

	// Fire refund event
	am.fireAARefundedEvent(characterID, nodeID, progress.CurrentRank, pointsRefunded)

	// Send notification
	if am.notifier != nil {
		am.notifier.NotifyAARefund(characterID, aaData.Name, pointsRefunded)
	}

	// Update statistics
	if am.statistics != nil {
		am.statistics.RecordAARefund(characterID, nodeID, pointsRefunded)
	}

	return nil
}

// GetAvailableAAs returns AAs available for a player in a specific tab
func (am *AAManager) GetAvailableAAs(characterID int32, tabID int8) ([]*AltAdvanceData, error) {
	// Get player state
	playerState := am.getPlayerState(characterID)
	if playerState == nil {
		return nil, fmt.Errorf("player state not found")
	}

	// Get all AAs for the tab
	allAAs := am.masterAAList.GetAAsByGroup(tabID)
	var availableAAs []*AltAdvanceData

	for _, aa := range allAAs {
		// Check if AA is available for this player
		if am.isAAAvailable(playerState, aa) {
			availableAAs = append(availableAAs, aa)
		}
	}

	return availableAAs, nil
}

// ChangeAATemplate changes the active AA template for a player
func (am *AAManager) ChangeAATemplate(characterID int32, templateID int8) error {
	// Get player state
	playerState := am.getPlayerState(characterID)
	if playerState == nil {
		return fmt.Errorf("player state not found")
	}

	// Validate template change
	if am.validator != nil {
		if err := am.validator.ValidateTemplateChange(playerState, templateID); err != nil {
			return fmt.Errorf("template change validation failed: %v", err)
		}
	}

	// Change template
	oldTemplate := playerState.ActiveTemplate
	playerState.mutex.Lock()
	playerState.ActiveTemplate = templateID
	playerState.needsSync = true
	playerState.mutex.Unlock()

	// Fire template change event
	am.fireTemplateChangedEvent(characterID, oldTemplate, templateID)

	return nil
}

// SaveAATemplate saves an AA template for a player
func (am *AAManager) SaveAATemplate(characterID int32, templateID int8, name string) error {
	// Get player state
	playerState := am.getPlayerState(characterID)
	if playerState == nil {
		return fmt.Errorf("player state not found")
	}

	// Create or update template
	template := playerState.Templates[templateID]
	if template == nil {
		template = NewAATemplate(templateID, name)
		playerState.Templates[templateID] = template
	} else {
		template.Name = name
		template.UpdatedAt = time.Now()
	}

	playerState.mutex.Lock()
	playerState.needsSync = true
	playerState.mutex.Unlock()

	// Fire template created event
	am.fireTemplateCreatedEvent(characterID, templateID, name)

	return nil
}

// GetAATemplates returns all AA templates for a player
func (am *AAManager) GetAATemplates(characterID int32) (map[int8]*AATemplate, error) {
	// Get player state
	playerState := am.getPlayerState(characterID)
	if playerState == nil {
		return nil, fmt.Errorf("player state not found")
	}

	// Return copy of templates
	templates := make(map[int8]*AATemplate)
	playerState.mutex.RLock()
	for id, template := range playerState.Templates {
		templates[id] = template
	}
	playerState.mutex.RUnlock()

	return templates, nil
}

// AwardAAPoints awards AA points to a player
func (am *AAManager) AwardAAPoints(characterID int32, points int32, reason string) error {
	// Get player state - this handles loading if needed
	playerState, err := am.GetPlayerAAState(characterID)
	if err != nil {
		return fmt.Errorf("failed to get player state: %v", err)
	}

	// Award points and capture values for events
	var oldTotal, newTotal int32
	playerState.mutex.Lock()
	oldTotal = playerState.TotalPoints
	playerState.TotalPoints += points
	playerState.AvailablePoints += points
	newTotal = playerState.TotalPoints
	playerState.needsSync = true
	playerState.mutex.Unlock()

	// Send notification
	if am.notifier != nil {
		am.notifier.NotifyAAPointsAwarded(characterID, points, reason)
	}

	// Fire points changed event with captured values
	am.firePlayerAAPointsChangedEvent(characterID, oldTotal, newTotal)

	return nil
}

// GetAAPoints returns AA point totals for a player
func (am *AAManager) GetAAPoints(characterID int32) (int32, int32, int32, error) {
	// Get player state
	playerState := am.getPlayerState(characterID)
	if playerState == nil {
		return 0, 0, 0, fmt.Errorf("player state not found")
	}

	playerState.mutex.RLock()
	defer playerState.mutex.RUnlock()

	return playerState.TotalPoints, playerState.SpentPoints, playerState.AvailablePoints, nil
}

// GetAA returns an AA by node ID
func (am *AAManager) GetAA(nodeID int32) (*AltAdvanceData, error) {
	aaData := am.masterAAList.GetAltAdvancementByNodeID(nodeID)
	if aaData == nil {
		return nil, fmt.Errorf("AA node %d not found", nodeID)
	}
	return aaData, nil
}

// GetAABySpellID returns an AA by spell ID
func (am *AAManager) GetAABySpellID(spellID int32) (*AltAdvanceData, error) {
	aaData := am.masterAAList.GetAltAdvancement(spellID)
	if aaData == nil {
		return nil, fmt.Errorf("AA with spell ID %d not found", spellID)
	}
	return aaData, nil
}

// GetAAsByGroup returns AAs for a specific group/tab
func (am *AAManager) GetAAsByGroup(group int8) ([]*AltAdvanceData, error) {
	return am.masterAAList.GetAAsByGroup(group), nil
}

// GetAAsByClass returns AAs available for a specific class
func (am *AAManager) GetAAsByClass(classID int8) ([]*AltAdvanceData, error) {
	return am.masterAAList.GetAAsByClass(classID), nil
}

// GetSystemStats returns system statistics
func (am *AAManager) GetSystemStats() *AAManagerStats {
	am.statsMutex.RLock()
	defer am.statsMutex.RUnlock()

	// Return copy of stats
	stats := am.stats
	return &stats
}

// GetPlayerStats returns player-specific statistics
func (am *AAManager) GetPlayerStats(characterID int32) map[string]interface{} {
	playerState := am.getPlayerState(characterID)
	if playerState == nil {
		return map[string]interface{}{"error": "player not found"}
	}

	playerState.mutex.RLock()
	defer playerState.mutex.RUnlock()

	return map[string]interface{}{
		"character_id":     characterID,
		"total_points":     playerState.TotalPoints,
		"spent_points":     playerState.SpentPoints,
		"available_points": playerState.AvailablePoints,
		"banked_points":    playerState.BankedPoints,
		"active_template":  playerState.ActiveTemplate,
		"aa_count":         len(playerState.AAProgress),
		"template_count":   len(playerState.Templates),
		"last_update":      playerState.lastUpdate,
	}
}

// SetConfig updates the manager configuration
func (am *AAManager) SetConfig(config AAManagerConfig) error {
	am.config = config
	return nil
}

// GetConfig returns the current configuration
func (am *AAManager) GetConfig() AAManagerConfig {
	return am.config
}

// Integration methods

// SetDatabase sets the database interface
func (am *AAManager) SetDatabase(db AADatabase) {
	am.database = db
}

// SetPacketHandler sets the packet handler interface
func (am *AAManager) SetPacketHandler(handler AAPacketHandler) {
	am.packetHandler = handler
}

// SetEventHandler adds an event handler
func (am *AAManager) SetEventHandler(handler AAEventHandler) {
	am.eventMutex.Lock()
	defer am.eventMutex.Unlock()

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

// SetValidator sets the validator interface
func (am *AAManager) SetValidator(validator AAValidator) {
	am.validator = validator
}

// SetNotifier sets the notifier interface
func (am *AAManager) SetNotifier(notifier AANotifier) {
	am.notifier = notifier
}

// SetStatistics sets the statistics interface
func (am *AAManager) SetStatistics(stats AAStatistics) {
	am.statistics = stats
}

// SetCache sets the cache interface
func (am *AAManager) SetCache(cache AACache) {
	am.cache = cache
}

// Helper methods

// getPlayerState gets a player state from cache
func (am *AAManager) getPlayerState(characterID int32) *AAPlayerState {
	am.statesMutex.RLock()
	defer am.statesMutex.RUnlock()

	return am.playerStates[characterID]
}

// performAAPurchase performs the actual AA purchase
func (am *AAManager) performAAPurchase(playerState *AAPlayerState, aaData *AltAdvanceData, targetRank int8) error {
	// Calculate cost
	pointsCost := int32(aaData.RankCost) * int32(targetRank)

	// Update player state - MUST acquire lock BEFORE checking points to prevent TOCTOU race
	playerState.mutex.Lock()
	defer playerState.mutex.Unlock()

	// Check if player has enough points (inside the lock to prevent race condition)
	if playerState.AvailablePoints < pointsCost {
		return fmt.Errorf("insufficient AA points: need %d, have %d", pointsCost, playerState.AvailablePoints)
	}

	// Create or update progress
	progress := playerState.AAProgress[aaData.NodeID]
	if progress == nil {
		progress = &PlayerAAData{
			CharacterID: playerState.CharacterID,
			NodeID:      aaData.NodeID,
			CurrentRank: 0,
			PointsSpent: 0,
			TemplateID:  playerState.ActiveTemplate,
			TabID:       aaData.Group,
			PurchasedAt: time.Now(),
			UpdatedAt:   time.Now(),
		}
		playerState.AAProgress[aaData.NodeID] = progress
	}

	// Update progress
	progress.CurrentRank = targetRank
	progress.PointsSpent = pointsCost
	progress.UpdatedAt = time.Now()

	// Update point totals
	playerState.SpentPoints += pointsCost
	playerState.AvailablePoints -= pointsCost
	playerState.needsSync = true

	return nil
}

// isAAAvailable checks if an AA is available for a player
func (am *AAManager) isAAAvailable(playerState *AAPlayerState, aaData *AltAdvanceData) bool {
	// Check if player meets minimum level requirement
	// Note: This would normally get player level from the actual player object
	// For now, we'll assume level requirements are met

	// Check class requirements
	// Note: This would normally check the player's actual class
	// For now, we'll assume class requirements are met

	// Check prerequisites
	if aaData.RankPrereqID > 0 {
		prereqProgress, exists := playerState.AAProgress[aaData.RankPrereqID]
		if !exists || prereqProgress.CurrentRank < aaData.RankPrereq {
			return false
		}
	}

	return true
}

// Background processing loops

// updateStatsLoop periodically updates statistics
func (am *AAManager) updateStatsLoop() {
	defer am.wg.Done()

	ticker := time.NewTicker(am.config.UpdateInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			am.updateStatistics()
		case <-am.stopChan:
			return
		}
	}
}

// autoSaveLoop periodically saves player states
func (am *AAManager) autoSaveLoop() {
	defer am.wg.Done()

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

	for {
		select {
		case <-ticker.C:
			am.saveAllPlayerStates()
		case <-am.stopChan:
			return
		}
	}
}

// updateStatistics updates system statistics
func (am *AAManager) updateStatistics() {
	am.statsMutex.Lock()
	defer am.statsMutex.Unlock()

	am.statesMutex.RLock()
	am.stats.ActivePlayers = int64(len(am.playerStates))

	var totalPointsSpent int64
	var totalPurchases int64

	for _, playerState := range am.playerStates {
		playerState.mutex.RLock()
		totalPointsSpent += int64(playerState.SpentPoints)
		totalPurchases += int64(len(playerState.AAProgress))
		playerState.mutex.RUnlock()
	}
	am.statesMutex.RUnlock()

	am.stats.TotalPointsSpent = totalPointsSpent
	am.stats.TotalAAPurchases = totalPurchases

	if am.stats.ActivePlayers > 0 {
		am.stats.AveragePointsSpent = float64(totalPointsSpent) / float64(am.stats.ActivePlayers)
	}

	am.stats.LastStatsUpdate = time.Now()
}

// saveAllPlayerStates saves all cached player states
func (am *AAManager) saveAllPlayerStates() {
	if am.database == nil {
		return
	}

	am.statesMutex.RLock()
	defer am.statesMutex.RUnlock()

	for _, playerState := range am.playerStates {
		if playerState.needsSync {
			if err := am.database.SavePlayerAA(playerState); err != nil {
				am.updateErrorStats("database_errors")
				continue
			}
			playerState.needsSync = false
		}
	}
}

// updateErrorStats updates error statistics
func (am *AAManager) updateErrorStats(errorType string) {
	am.statsMutex.Lock()
	defer am.statsMutex.Unlock()

	switch errorType {
	case "validation_errors":
		am.stats.ValidationErrors++
	case "database_errors":
		am.stats.DatabaseErrors++
	case "packet_errors":
		am.stats.PacketErrors++
	}
}

// Event firing methods

// fireSystemLoadedEvent fires a system loaded event
func (am *AAManager) fireSystemLoadedEvent(totalAAs, totalNodes int32) {
	am.eventMutex.RLock()
	defer am.eventMutex.RUnlock()

	for _, handler := range am.eventHandlers {
		go handler.OnAASystemLoaded(totalAAs, totalNodes)
	}
}

// fireDataReloadedEvent fires a data reloaded event
func (am *AAManager) fireDataReloadedEvent() {
	am.eventMutex.RLock()
	defer am.eventMutex.RUnlock()

	for _, handler := range am.eventHandlers {
		go handler.OnAADataReloaded()
	}
}

// firePlayerAALoadedEvent fires a player AA loaded event
func (am *AAManager) firePlayerAALoadedEvent(characterID int32, playerState *AAPlayerState) {
	am.eventMutex.RLock()
	defer am.eventMutex.RUnlock()

	for _, handler := range am.eventHandlers {
		go handler.OnPlayerAALoaded(characterID, playerState)
	}
}

// fireAAPurchasedEvent fires an AA purchased event
func (am *AAManager) fireAAPurchasedEvent(characterID int32, nodeID int32, newRank int8, pointsSpent int32) {
	am.eventMutex.RLock()
	defer am.eventMutex.RUnlock()

	for _, handler := range am.eventHandlers {
		go handler.OnAAPurchased(characterID, nodeID, newRank, pointsSpent)
	}
}

// fireAARefundedEvent fires an AA refunded event
func (am *AAManager) fireAARefundedEvent(characterID int32, nodeID int32, oldRank int8, pointsRefunded int32) {
	am.eventMutex.RLock()
	defer am.eventMutex.RUnlock()

	for _, handler := range am.eventHandlers {
		go handler.OnAARefunded(characterID, nodeID, oldRank, pointsRefunded)
	}
}

// fireTemplateChangedEvent fires a template changed event
func (am *AAManager) fireTemplateChangedEvent(characterID int32, oldTemplate, newTemplate int8) {
	am.eventMutex.RLock()
	defer am.eventMutex.RUnlock()

	for _, handler := range am.eventHandlers {
		go handler.OnAATemplateChanged(characterID, oldTemplate, newTemplate)
	}
}

// fireTemplateCreatedEvent fires a template created event
func (am *AAManager) fireTemplateCreatedEvent(characterID int32, templateID int8, name string) {
	am.eventMutex.RLock()
	defer am.eventMutex.RUnlock()

	for _, handler := range am.eventHandlers {
		go handler.OnAATemplateCreated(characterID, templateID, name)
	}
}

// firePlayerAAPointsChangedEvent fires a player AA points changed event
func (am *AAManager) firePlayerAAPointsChangedEvent(characterID int32, oldPoints, newPoints int32) {
	am.eventMutex.RLock()
	defer am.eventMutex.RUnlock()

	for _, handler := range am.eventHandlers {
		go handler.OnPlayerAAPointsChanged(characterID, oldPoints, newPoints)
	}
}
