package alt_advancement

import (
	"sync"
	"sync/atomic"
	"testing"
)

// TestAAManagerConcurrentPlayerAccess tests concurrent access to player states
func TestAAManagerConcurrentPlayerAccess(t *testing.T) {
	config := DefaultAAManagerConfig()
	manager := NewAAManager(config)
	
	// Set up mock database
	mockDB := &mockAADatabase{}
	manager.SetDatabase(mockDB)
	
	// Test concurrent access to the same player
	const numGoroutines = 100
	const characterID = int32(123)
	
	var wg sync.WaitGroup
	var successCount int64
	
	// Launch multiple goroutines trying to get the same player state
	for i := 0; i < numGoroutines; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			
			state, err := manager.GetPlayerAAState(characterID)
			if err != nil {
				t.Errorf("Failed to get player state: %v", err)
				return
			}
			
			if state == nil {
				t.Error("Got nil player state")
				return
			}
			
			if state.CharacterID != characterID {
				t.Errorf("Wrong character ID: expected %d, got %d", characterID, state.CharacterID)
				return
			}
			
			atomic.AddInt64(&successCount, 1)
		}()
	}
	
	wg.Wait()
	
	if atomic.LoadInt64(&successCount) != numGoroutines {
		t.Errorf("Expected %d successful operations, got %d", numGoroutines, successCount)
	}
	
	// Verify only one instance was created in cache
	manager.statesMutex.RLock()
	cachedStates := len(manager.playerStates)
	manager.statesMutex.RUnlock()
	
	if cachedStates != 1 {
		t.Errorf("Expected 1 cached state, got %d", cachedStates)
	}
}

// TestAAManagerConcurrentMultiplePlayer tests concurrent access to different players
func TestAAManagerConcurrentMultiplePlayer(t *testing.T) {
	config := DefaultAAManagerConfig()
	manager := NewAAManager(config)
	
	// Set up mock database
	mockDB := &mockAADatabase{}
	manager.SetDatabase(mockDB)
	
	const numPlayers = 50
	const goroutinesPerPlayer = 10
	
	var wg sync.WaitGroup
	var successCount int64
	
	// Launch multiple goroutines for different players
	for playerID := int32(1); playerID <= numPlayers; playerID++ {
		for j := 0; j < goroutinesPerPlayer; j++ {
			wg.Add(1)
			go func(id int32) {
				defer wg.Done()
				
				state, err := manager.GetPlayerAAState(id)
				if err != nil {
					t.Errorf("Failed to get player state for %d: %v", id, err)
					return
				}
				
				if state == nil {
					t.Errorf("Got nil player state for %d", id)
					return
				}
				
				if state.CharacterID != id {
					t.Errorf("Wrong character ID: expected %d, got %d", id, state.CharacterID)
					return
				}
				
				atomic.AddInt64(&successCount, 1)
			}(playerID)
		}
	}
	
	wg.Wait()
	
	expectedSuccess := int64(numPlayers * goroutinesPerPlayer)
	if atomic.LoadInt64(&successCount) != expectedSuccess {
		t.Errorf("Expected %d successful operations, got %d", expectedSuccess, successCount)
	}
	
	// Verify correct number of cached states
	manager.statesMutex.RLock()
	cachedStates := len(manager.playerStates)
	manager.statesMutex.RUnlock()
	
	if cachedStates != numPlayers {
		t.Errorf("Expected %d cached states, got %d", numPlayers, cachedStates)
	}
}

// TestConcurrentAAPurchases tests concurrent AA purchases
func TestConcurrentAAPurchases(t *testing.T) {
	config := DefaultAAManagerConfig()
	manager := NewAAManager(config)
	
	// Set up mock database
	mockDB := &mockAADatabase{}
	manager.SetDatabase(mockDB)
	
	// Add test AAs
	for i := 1; i <= 10; i++ {
		aa := &AltAdvanceData{
			SpellID:  int32(i * 100),
			NodeID:   int32(i * 200),
			Name:     "Test AA",
			Group:    AA_CLASS,
			MaxRank:  5,
			RankCost: 1, // Low cost for testing
			MinLevel: 1,
		}
		manager.masterAAList.AddAltAdvancement(aa)
	}
	
	// Get player state and give it points
	state, err := manager.GetPlayerAAState(123)
	if err != nil {
		t.Fatalf("Failed to get player state: %v", err)
	}
	
	// Give player plenty of points
	state.TotalPoints = 1000
	state.AvailablePoints = 1000
	
	const numGoroutines = 20
	var wg sync.WaitGroup
	var successCount, errorCount int64
	
	// Concurrent purchases
	for i := 0; i < numGoroutines; i++ {
		wg.Add(1)
		go func(goroutineID int) {
			defer wg.Done()
			
			// Try to purchase different AAs
			aaNodeID := int32(200 + (goroutineID%10)*200) // Spread across different AAs
			
			err := manager.PurchaseAA(123, aaNodeID, 1)
			if err != nil {
				atomic.AddInt64(&errorCount, 1)
				// Some errors expected due to race conditions or insufficient points
			} else {
				atomic.AddInt64(&successCount, 1)
			}
		}(i)
	}
	
	wg.Wait()
	
	t.Logf("Successful purchases: %d, Errors: %d", successCount, errorCount)
	
	// Verify final state consistency
	state.mutex.RLock()
	finalAvailable := state.AvailablePoints
	finalSpent := state.SpentPoints
	finalTotal := state.TotalPoints
	numProgress := len(state.AAProgress)
	state.mutex.RUnlock()
	
	// Basic consistency checks
	if finalAvailable+finalSpent != finalTotal {
		t.Errorf("Point consistency check failed: available(%d) + spent(%d) != total(%d)", 
			finalAvailable, finalSpent, finalTotal)
	}
	
	if numProgress > int(successCount) {
		t.Errorf("More progress entries (%d) than successful purchases (%d)", numProgress, successCount)
	}
	
	t.Logf("Final state: Total=%d, Spent=%d, Available=%d, Progress entries=%d", 
		finalTotal, finalSpent, finalAvailable, numProgress)
}

// TestConcurrentAAPointAwarding tests concurrent point awarding
func TestConcurrentAAPointAwarding(t *testing.T) {
	config := DefaultAAManagerConfig()
	manager := NewAAManager(config)
	
	// Set up mock database
	mockDB := &mockAADatabase{}
	manager.SetDatabase(mockDB)
	
	const characterID = int32(123)
	const numGoroutines = 100
	const pointsPerAward = int32(10)
	
	var wg sync.WaitGroup
	var successCount int64
	
	// Concurrent point awarding
	for i := 0; i < numGoroutines; i++ {
		wg.Add(1)
		go func(goroutineID int) {
			defer wg.Done()
			
			err := manager.AwardAAPoints(characterID, pointsPerAward, "Concurrent test")
			if err != nil {
				t.Errorf("Failed to award points: %v", err)
				return
			}
			
			atomic.AddInt64(&successCount, 1)
		}(i)
	}
	
	wg.Wait()
	
	if atomic.LoadInt64(&successCount) != numGoroutines {
		t.Errorf("Expected %d successful awards, got %d", numGoroutines, successCount)
	}
	
	// Verify final point total
	total, spent, available, err := manager.GetAAPoints(characterID)
	if err != nil {
		t.Fatalf("Failed to get AA points: %v", err)
	}
	
	expectedTotal := pointsPerAward * numGoroutines
	if total != expectedTotal {
		t.Errorf("Expected total points %d, got %d", expectedTotal, total)
	}
	
	if spent != 0 {
		t.Errorf("Expected 0 spent points, got %d", spent)
	}
	
	if available != expectedTotal {
		t.Errorf("Expected available points %d, got %d", expectedTotal, available)
	}
}

// TestMasterAAListConcurrentOperations tests thread safety of MasterAAList
func TestMasterAAListConcurrentOperations(t *testing.T) {
	masterList := NewMasterAAList()
	
	// Pre-populate with some AAs
	for i := 1; i <= 100; i++ {
		aa := &AltAdvanceData{
			SpellID:  int32(i * 100),
			NodeID:   int32(i * 200),
			Name:     "Test AA",
			Group:    AA_CLASS,
			MaxRank:  5,
			RankCost: 2,
		}
		masterList.AddAltAdvancement(aa)
	}
	
	const numReaders = 50
	const numWriters = 10
	const operationsPerGoroutine = 100
	
	var wg sync.WaitGroup
	var readOps, writeOps int64
	
	// Reader goroutines
	for i := 0; i < numReaders; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			
			for j := 0; j < operationsPerGoroutine; j++ {
				// Mix different read operations
				switch j % 5 {
				case 0:
					masterList.GetAltAdvancement(100)
				case 1:
					masterList.GetAltAdvancementByNodeID(200)
				case 2:
					masterList.GetAAsByGroup(AA_CLASS)
				case 3:
					masterList.Size()
				case 4:
					masterList.GetAllAAs()
				}
				atomic.AddInt64(&readOps, 1)
			}
		}()
	}
	
	// Writer goroutines (adding new AAs)
	for i := 0; i < numWriters; i++ {
		wg.Add(1)
		go func(writerID int) {
			defer wg.Done()
			
			for j := 0; j < operationsPerGoroutine; j++ {
				// Create unique AAs for each writer
				baseID := (writerID + 1000) * 1000 + j
				aa := &AltAdvanceData{
					SpellID:  int32(baseID),
					NodeID:   int32(baseID + 100000),
					Name:     "Concurrent AA",
					Group:    AA_CLASS,
					MaxRank:  5,
					RankCost: 2,
				}
				
				err := masterList.AddAltAdvancement(aa)
				if err != nil {
					// Some errors expected due to potential duplicates
					continue
				}
				atomic.AddInt64(&writeOps, 1)
			}
		}(i)
	}
	
	wg.Wait()
	
	t.Logf("Read operations: %d, Write operations: %d", readOps, writeOps)
	
	// Verify final state
	finalSize := masterList.Size()
	if finalSize < 100 {
		t.Errorf("Expected at least 100 AAs, got %d", finalSize)
	}
	
	t.Logf("Final AA count: %d", finalSize)
}

// TestMasterAANodeListConcurrentOperations tests thread safety of MasterAANodeList
func TestMasterAANodeListConcurrentOperations(t *testing.T) {
	nodeList := NewMasterAANodeList()
	
	// Pre-populate with some nodes
	for i := 1; i <= 50; i++ {
		node := &TreeNodeData{
			ClassID:  int32(i % 10 + 1), // Classes 1-10
			TreeID:   int32(i * 100),
			AATreeID: int32(i * 200),
		}
		nodeList.AddTreeNode(node)
	}
	
	const numReaders = 30
	const numWriters = 5
	const operationsPerGoroutine = 100
	
	var wg sync.WaitGroup
	var readOps, writeOps int64
	
	// Reader goroutines
	for i := 0; i < numReaders; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			
			for j := 0; j < operationsPerGoroutine; j++ {
				// Mix different read operations
				switch j % 4 {
				case 0:
					nodeList.GetTreeNode(100)
				case 1:
					nodeList.GetTreeNodesByClass(1)
				case 2:
					nodeList.Size()
				case 3:
					nodeList.GetTreeNodes()
				}
				atomic.AddInt64(&readOps, 1)
			}
		}()
	}
	
	// Writer goroutines
	for i := 0; i < numWriters; i++ {
		wg.Add(1)
		go func(writerID int) {
			defer wg.Done()
			
			for j := 0; j < operationsPerGoroutine; j++ {
				// Create unique nodes for each writer
				baseID := (writerID + 1000) * 1000 + j
				node := &TreeNodeData{
					ClassID:  int32(writerID%5 + 1),
					TreeID:   int32(baseID),
					AATreeID: int32(baseID + 100000),
				}
				
				err := nodeList.AddTreeNode(node)
				if err != nil {
					// Some errors expected due to potential duplicates
					continue
				}
				atomic.AddInt64(&writeOps, 1)
			}
		}(i)
	}
	
	wg.Wait()
	
	t.Logf("Read operations: %d, Write operations: %d", readOps, writeOps)
	
	// Verify final state
	finalSize := nodeList.Size()
	if finalSize < 50 {
		t.Errorf("Expected at least 50 nodes, got %d", finalSize)
	}
	
	t.Logf("Final node count: %d", finalSize)
}

// TestAAPlayerStateConcurrentAccess tests thread safety of AAPlayerState
func TestAAPlayerStateConcurrentAccess(t *testing.T) {
	playerState := NewAAPlayerState(123)
	
	// Give player some initial points
	playerState.TotalPoints = 1000
	playerState.AvailablePoints = 1000
	
	const numGoroutines = 100
	var wg sync.WaitGroup
	
	// Concurrent operations on player state
	for i := 0; i < numGoroutines; i++ {
		wg.Add(1)
		go func(goroutineID int) {
			defer wg.Done()
			
			// Mix of different operations
			switch goroutineID % 4 {
			case 0:
				// Add AA progress
				progress := &PlayerAAData{
					CharacterID: 123,
					NodeID:      int32(goroutineID + 1000),
					CurrentRank: 1,
					PointsSpent: 2,
				}
				playerState.AddAAProgress(progress)
				
			case 1:
				// Update points
				playerState.UpdatePoints(1000, int32(goroutineID), 0)
				
			case 2:
				// Get AA progress
				playerState.GetAAProgress(int32(goroutineID + 1000))
				
			case 3:
				// Calculate spent points
				playerState.CalculateSpentPoints()
			}
		}(i)
	}
	
	wg.Wait()
	
	// Verify state is still consistent
	playerState.mutex.RLock()
	totalPoints := playerState.TotalPoints
	progressCount := len(playerState.AAProgress)
	playerState.mutex.RUnlock()
	
	if totalPoints != 1000 {
		t.Errorf("Expected total points to remain 1000, got %d", totalPoints)
	}
	
	t.Logf("Final progress entries: %d", progressCount)
}

// TestConcurrentSystemOperations tests mixed system operations
func TestConcurrentSystemOperations(t *testing.T) {
	config := DefaultAAManagerConfig()
	manager := NewAAManager(config)
	
	// Set up mock database
	mockDB := &mockAADatabase{}
	manager.SetDatabase(mockDB)
	
	// Add some test AAs
	for i := 1; i <= 20; i++ {
		aa := &AltAdvanceData{
			SpellID:  int32(i * 100),
			NodeID:   int32(i * 200),
			Name:     "Test AA",
			Group:    int8(i % 3), // Mix groups
			MaxRank:  5,
			RankCost: 2,
			MinLevel: 1,
		}
		manager.masterAAList.AddAltAdvancement(aa)
	}
	
	const numGoroutines = 50
	var wg sync.WaitGroup
	var operations int64
	
	for i := 0; i < numGoroutines; i++ {
		wg.Add(1)
		go func(goroutineID int) {
			defer wg.Done()
			
			playerID := int32(goroutineID%10 + 1) // 10 different players
			
			// Mix of operations
			switch goroutineID % 6 {
			case 0:
				// Get player state
				manager.GetPlayerAAState(playerID)
				
			case 1:
				// Award points
				manager.AwardAAPoints(playerID, 50, "Test")
				
			case 2:
				// Get AA points
				manager.GetAAPoints(playerID)
				
			case 3:
				// Get AAs by group
				manager.GetAAsByGroup(AA_CLASS)
				
			case 4:
				// Get system stats
				manager.GetSystemStats()
				
			case 5:
				// Try to purchase AA (might fail, that's ok)
				manager.PurchaseAA(playerID, 200, 1)
			}
			
			atomic.AddInt64(&operations, 1)
		}(i)
	}
	
	wg.Wait()
	
	if atomic.LoadInt64(&operations) != numGoroutines {
		t.Errorf("Expected %d operations, got %d", numGoroutines, operations)
	}
	
	t.Logf("Completed %d concurrent system operations", operations)
}