package factions

import (
	"sync"
	"testing"
	"time"
)

// Stress test MasterFactionList with concurrent operations
func TestMasterFactionListConcurrency(t *testing.T) {
	mfl := NewMasterFactionList()
	
	// Pre-populate with some factions
	for i := int32(1); i <= 10; i++ {
		faction := NewFaction(i, "Test Faction", "Test", "Test faction")
		mfl.AddFaction(faction)
	}
	
	const numGoroutines = 100
	const operationsPerGoroutine = 100
	
	var wg sync.WaitGroup
	
	t.Run("ConcurrentFactionAccess", func(t *testing.T) {
		wg.Add(numGoroutines)
		
		for i := 0; i < numGoroutines; i++ {
			go func(goroutineID int) {
				defer wg.Done()
				
				for j := 0; j < operationsPerGoroutine; j++ {
					factionID := int32((goroutineID%10) + 1)
					
					// Mix of read operations
					faction := mfl.GetFaction(factionID)
					if faction == nil {
						t.Errorf("Goroutine %d: Expected faction %d to exist", goroutineID, factionID)
					}
					
					_ = mfl.GetFactionByName("Test Faction")
					_ = mfl.HasFaction(factionID)
					_ = mfl.GetFactionCount()
					_ = mfl.GetDefaultFactionValue(factionID)
					_ = mfl.GetIncreaseAmount(factionID)
					_ = mfl.GetDecreaseAmount(factionID)
				}
			}(i)
		}
		
		wg.Wait()
	})
	
	t.Run("ConcurrentRelationshipManagement", func(t *testing.T) {
		wg.Add(numGoroutines)
		
		for i := 0; i < numGoroutines; i++ {
			go func(goroutineID int) {
				defer wg.Done()
				
				for j := 0; j < operationsPerGoroutine; j++ {
					factionID := int32((goroutineID%10) + 1)
					targetID := int32(((goroutineID+1)%10) + 1)
					
					// Concurrent relationship operations
					if goroutineID%2 == 0 {
						mfl.AddHostileFaction(factionID, targetID)
						_ = mfl.GetHostileFactions(factionID)
					} else {
						mfl.AddFriendlyFaction(factionID, targetID)
						_ = mfl.GetFriendlyFactions(factionID)
					}
				}
			}(i)
		}
		
		wg.Wait()
	})
	
	t.Run("ConcurrentFactionModification", func(t *testing.T) {
		wg.Add(numGoroutines)
		
		for i := 0; i < numGoroutines; i++ {
			go func(goroutineID int) {
				defer wg.Done()
				
				for j := 0; j < operationsPerGoroutine; j++ {
					newFactionID := int32(1000 + goroutineID*operationsPerGoroutine + j)
					faction := NewFaction(newFactionID, "Concurrent Faction", "Test", "Concurrent test")
					
					err := mfl.AddFaction(faction)
					if err != nil {
						t.Errorf("Goroutine %d: Failed to add faction %d: %v", goroutineID, newFactionID, err)
					}
					
					// Verify it was added
					retrieved := mfl.GetFaction(newFactionID)
					if retrieved == nil {
						t.Errorf("Goroutine %d: Failed to retrieve faction %d", goroutineID, newFactionID)
					}
				}
			}(i)
		}
		
		wg.Wait()
	})
}

// Stress test PlayerFaction with concurrent operations
func TestPlayerFactionConcurrency(t *testing.T) {
	mfl := NewMasterFactionList()
	
	// Add test factions with proper values
	for i := int32(1); i <= 10; i++ {
		faction := NewFaction(i, "Test Faction", "Test", "Test faction")
		faction.PositiveChange = 100
		faction.NegativeChange = 50
		mfl.AddFaction(faction)
	}
	
	pf := NewPlayerFaction(mfl)
	
	const numGoroutines = 100
	const operationsPerGoroutine = 100
	
	var wg sync.WaitGroup
	
	t.Run("ConcurrentFactionValueOperations", func(t *testing.T) {
		wg.Add(numGoroutines)
		
		for i := 0; i < numGoroutines; i++ {
			go func(goroutineID int) {
				defer wg.Done()
				
				for j := 0; j < operationsPerGoroutine; j++ {
					factionID := int32((goroutineID%10) + 1)
					
					// Mix of faction value operations
					switch j % 4 {
					case 0:
						pf.IncreaseFaction(factionID, 10)
					case 1:
						pf.DecreaseFaction(factionID, 5)
					case 2:
						pf.SetFactionValue(factionID, int32(goroutineID*100))
					case 3:
						_ = pf.GetFactionValue(factionID)
						_ = pf.GetCon(factionID)
						_ = pf.GetPercent(factionID)
					}
				}
			}(i)
		}
		
		wg.Wait()
	})
	
	t.Run("ConcurrentUpdateManagement", func(t *testing.T) {
		wg.Add(numGoroutines)
		
		for i := 0; i < numGoroutines; i++ {
			go func(goroutineID int) {
				defer wg.Done()
				
				for j := 0; j < operationsPerGoroutine; j++ {
					factionID := int32((goroutineID%10) + 1)
					
					// Operations that trigger updates
					pf.IncreaseFaction(factionID, 1)
					
					// Check for pending updates
					_ = pf.HasPendingUpdates()
					_ = pf.GetPendingUpdates()
					
					// Occasionally clear updates
					if j%10 == 0 {
						pf.ClearPendingUpdates()
					}
				}
			}(i)
		}
		
		wg.Wait()
	})
	
	t.Run("ConcurrentPacketGeneration", func(t *testing.T) {
		wg.Add(numGoroutines)
		
		for i := 0; i < numGoroutines; i++ {
			go func(goroutineID int) {
				defer wg.Done()
				
				for j := 0; j < operationsPerGoroutine; j++ {
					// Trigger faction updates
					factionID := int32((goroutineID%10) + 1)
					pf.IncreaseFaction(factionID, 1)
					
					// Generate packets concurrently
					_, err := pf.FactionUpdate(int16(goroutineID % 10))
					if err != nil {
						t.Errorf("Goroutine %d: FactionUpdate failed: %v", goroutineID, err)
					}
				}
			}(i)
		}
		
		wg.Wait()
	})
}

// Stress test Manager with concurrent operations
func TestManagerConcurrency(t *testing.T) {
	manager := NewManager(nil, nil) // No database or logger for testing
	
	const numGoroutines = 100
	const operationsPerGoroutine = 100
	
	var wg sync.WaitGroup
	
	t.Run("ConcurrentStatisticsOperations", func(t *testing.T) {
		wg.Add(numGoroutines)
		
		for i := 0; i < numGoroutines; i++ {
			go func(goroutineID int) {
				defer wg.Done()
				
				for j := 0; j < operationsPerGoroutine; j++ {
					factionID := int32((goroutineID%10) + 1)
					
					// Mix of statistics operations
					switch j % 4 {
					case 0:
						manager.RecordFactionIncrease(factionID)
					case 1:
						manager.RecordFactionDecrease(factionID)
					case 2:
						_ = manager.GetStatistics()
					case 3:
						_ = manager.GetFactionCount()
					}
				}
			}(i)
		}
		
		wg.Wait()
		
		// Verify statistics consistency
		stats := manager.GetStatistics()
		totalChanges := stats["total_faction_changes"].(int64)
		increases := stats["faction_increases"].(int64)
		decreases := stats["faction_decreases"].(int64)
		
		if totalChanges != increases+decreases {
			t.Errorf("Statistics inconsistency: total %d != increases %d + decreases %d", 
				totalChanges, increases, decreases)
		}
	})
	
	t.Run("ConcurrentFactionManagement", func(t *testing.T) {
		wg.Add(numGoroutines)
		
		for i := 0; i < numGoroutines; i++ {
			go func(goroutineID int) {
				defer wg.Done()
				
				for j := 0; j < operationsPerGoroutine; j++ {
					factionID := int32(2000 + goroutineID*operationsPerGoroutine + j)
					faction := NewFaction(factionID, "Manager Test", "Test", "Manager test")
					
					// Add faction
					err := manager.AddFaction(faction)
					if err != nil {
						t.Errorf("Goroutine %d: Failed to add faction %d: %v", goroutineID, factionID, err)
						continue
					}
					
					// Read operations
					_ = manager.GetFaction(factionID)
					_ = manager.GetFactionByName("Manager Test")
				}
			}(i)
		}
		
		wg.Wait()
	})
	
	t.Run("ConcurrentPlayerFactionCreation", func(t *testing.T) {
		wg.Add(numGoroutines)
		
		for i := 0; i < numGoroutines; i++ {
			go func(goroutineID int) {
				defer wg.Done()
				
				for j := 0; j < operationsPerGoroutine; j++ {
					// Create player factions concurrently
					pf := manager.CreatePlayerFaction()
					if pf == nil {
						t.Errorf("Goroutine %d: CreatePlayerFaction returned nil", goroutineID)
					}
				}
			}(i)
		}
		
		wg.Wait()
		
		// Check statistics
		stats := manager.GetStatistics()
		expectedPlayers := int64(numGoroutines * operationsPerGoroutine)
		actualPlayers := stats["players_with_factions"].(int64)
		
		if actualPlayers != expectedPlayers {
			t.Errorf("Expected %d players with factions, got %d", expectedPlayers, actualPlayers)
		}
	})
}

// Stress test EntityFactionAdapter with concurrent operations
func TestEntityFactionAdapterConcurrency(t *testing.T) {
	manager := NewManager(nil, nil)
	
	// Mock entity
	entity := &mockEntity{id: 123, name: "Test Entity", dbID: 456}
	adapter := NewEntityFactionAdapter(entity, manager, nil)
	
	const numGoroutines = 100
	const operationsPerGoroutine = 100
	
	var wg sync.WaitGroup
	
	t.Run("ConcurrentFactionIDOperations", func(t *testing.T) {
		wg.Add(numGoroutines)
		
		for i := 0; i < numGoroutines; i++ {
			go func(goroutineID int) {
				defer wg.Done()
				
				for j := 0; j < operationsPerGoroutine; j++ {
					factionID := int32((goroutineID%10) + 1)
					
					// Mix of faction ID operations
					switch j % 3 {
					case 0:
						adapter.SetFactionID(factionID)
					case 1:
						_ = adapter.GetFactionID()
					case 2:
						_ = adapter.GetFaction()
						_ = adapter.GetFactionName()
					}
				}
			}(i)
		}
		
		wg.Wait()
	})
	
	t.Run("ConcurrentRelationshipChecks", func(t *testing.T) {
		// Set up some factions first
		for i := int32(1); i <= 10; i++ {
			faction := NewFaction(i, "Test Faction", "Test", "Test faction")
			manager.AddFaction(faction)
		}
		
		// Set up relationships
		mfl := manager.GetMasterFactionList()
		mfl.AddHostileFaction(1, 2)
		mfl.AddFriendlyFaction(1, 3)
		
		adapter.SetFactionID(1)
		
		wg.Add(numGoroutines)
		
		for i := 0; i < numGoroutines; i++ {
			go func(goroutineID int) {
				defer wg.Done()
				
				for j := 0; j < operationsPerGoroutine; j++ {
					otherFactionID := int32((goroutineID%10) + 1)
					
					// Concurrent relationship checks
					_ = adapter.IsHostileToFaction(otherFactionID)
					_ = adapter.IsFriendlyToFaction(otherFactionID)
					
					// Validation
					_ = adapter.ValidateFaction()
				}
			}(i)
		}
		
		wg.Wait()
	})
}

// Mock entity for testing
type mockEntity struct {
	id   int32
	name string
	dbID int32
}

func (e *mockEntity) GetID() int32 {
	return e.id
}

func (e *mockEntity) GetName() string {
	return e.name
}

func (e *mockEntity) GetDatabaseID() int32 {
	return e.dbID
}

// Test for potential deadlocks
func TestDeadlockPrevention(t *testing.T) {
	mfl := NewMasterFactionList()
	manager := NewManager(nil, nil)
	
	// Add test factions
	for i := int32(1); i <= 10; i++ {
		faction := NewFaction(i, "Test Faction", "Test", "Test faction")
		manager.AddFaction(faction)
	}
	
	const numGoroutines = 50
	var wg sync.WaitGroup
	
	// Test potential deadlock scenarios
	t.Run("MixedOperations", func(t *testing.T) {
		done := make(chan bool, 1)
		
		// Set a timeout to detect deadlocks
		go func() {
			time.Sleep(10 * time.Second)
			select {
			case <-done:
				return
			default:
				t.Error("Potential deadlock detected - test timed out")
				t.FailNow()
			}
		}()
		
		wg.Add(numGoroutines)
		
		for i := 0; i < numGoroutines; i++ {
			go func(goroutineID int) {
				defer wg.Done()
				
				for j := 0; j < 100; j++ {
					factionID := int32((goroutineID%10) + 1)
					
					// Mix operations that could potentially deadlock
					switch j % 6 {
					case 0:
						_ = mfl.GetFaction(factionID)
						_ = manager.GetFaction(factionID)
					case 1:
						_ = mfl.GetFactionCount()
						_ = manager.GetFactionCount()
					case 2:
						mfl.AddHostileFaction(factionID, factionID+1)
						_ = manager.GetStatistics()
					case 3:
						_ = mfl.GetAllFactions()
						manager.RecordFactionIncrease(factionID)
					case 4:
						_ = mfl.ValidateFactions()
						_ = manager.ValidateAllFactions()
					case 5:
						pf := manager.CreatePlayerFaction()
						pf.IncreaseFaction(factionID, 10)
						_ = pf.GetFactionValue(factionID)
					}
				}
			}(i)
		}
		
		wg.Wait()
		done <- true
	})
}

// Race condition detection test - run with -race flag
func TestRaceConditions(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping race condition test in short mode")
	}
	
	// This test is designed to be run with: go test -race
	mfl := NewMasterFactionList()
	manager := NewManager(nil, nil)
	
	// Rapid concurrent operations to trigger race conditions
	const numGoroutines = 200
	const operationsPerGoroutine = 50
	
	var wg sync.WaitGroup
	wg.Add(numGoroutines)
	
	for i := 0; i < numGoroutines; i++ {
		go func(goroutineID int) {
			defer wg.Done()
			
			for j := 0; j < operationsPerGoroutine; j++ {
				factionID := int32((goroutineID%20) + 1)
				
				// Rapid-fire operations
				faction := NewFaction(factionID, "Race Test", "Test", "Race test")
				mfl.AddFaction(faction)
				_ = mfl.GetFaction(factionID)
				mfl.AddHostileFaction(factionID, factionID+1)
				_ = mfl.GetHostileFactions(factionID)
				
				manager.AddFaction(faction)
				manager.RecordFactionIncrease(factionID)
				_ = manager.GetStatistics()
				
				pf := manager.CreatePlayerFaction()
				pf.IncreaseFaction(factionID, 1)
				_ = pf.GetFactionValue(factionID)
			}
		}(i)
	}
	
	wg.Wait()
}