package ground_spawn

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

// Mock implementations are in test_utils.go

// Stress test GroundSpawn with concurrent operations
func TestGroundSpawnConcurrency(t *testing.T) {
	config := GroundSpawnConfig{
		GroundSpawnID:      1,
		CollectionSkill:    SkillGathering,
		NumberHarvests:     10,
		AttemptsPerHarvest: 2,
		RandomizeHeading:   true,
		Location: SpawnLocation{
			X: 100.0, Y: 200.0, Z: 300.0, Heading: 45.0, GridID: 1,
		},
		Name:        "Test Node",
		Description: "A test harvestable node",
	}

	gs := NewGroundSpawn(config)

	const numGoroutines = 100
	const operationsPerGoroutine = 100

	var wg sync.WaitGroup

	t.Run("ConcurrentGetterSetterOperations", func(t *testing.T) {
		wg.Add(numGoroutines)

		for i := range numGoroutines {
			go func(goroutineID int) {
				defer wg.Done()

				for j := range operationsPerGoroutine {
					switch j % 8 {
					case 0:
						gs.SetNumberHarvests(int8(goroutineID % 10))
					case 1:
						_ = gs.GetNumberHarvests()
					case 2:
						gs.SetAttemptsPerHarvest(int8(goroutineID % 5))
					case 3:
						_ = gs.GetAttemptsPerHarvest()
					case 4:
						gs.SetCollectionSkill(SkillMining)
					case 5:
						_ = gs.GetCollectionSkill()
					case 6:
						gs.SetRandomizeHeading(goroutineID%2 == 0)
					case 7:
						_ = gs.GetRandomizeHeading()
					}
				}
			}(i)
		}

		wg.Wait()
	})

	t.Run("ConcurrentStateChecks", func(t *testing.T) {
		wg.Add(numGoroutines)

		for i := range numGoroutines {
			go func(goroutineID int) {
				defer wg.Done()

				for j := range operationsPerGoroutine {
					switch j % 4 {
					case 0:
						_ = gs.IsDepleted()
					case 1:
						_ = gs.IsAvailable()
					case 2:
						_ = gs.GetHarvestMessageName(true, false)
					case 3:
						_ = gs.GetHarvestSpellType()
					}
				}
			}(i)
		}

		wg.Wait()
	})

	t.Run("ConcurrentCopyOperations", func(t *testing.T) {
		wg.Add(numGoroutines)

		for i := range numGoroutines {
			go func(goroutineID int) {
				defer wg.Done()

				for j := range operationsPerGoroutine {
					// Test concurrent copying while modifying
					if j%2 == 0 {
						copy := gs.Copy()
						if copy == nil {
							t.Errorf("Goroutine %d: Copy returned nil", goroutineID)
						}
					} else {
						gs.SetNumberHarvests(int8(goroutineID % 5))
					}
				}
			}(i)
		}

		wg.Wait()
	})

	t.Run("ConcurrentRespawnOperations", func(t *testing.T) {
		wg.Add(numGoroutines)

		for i := range numGoroutines {
			go func(goroutineID int) {
				defer wg.Done()

				for j := range operationsPerGoroutine {
					if j%10 == 0 {
						gs.Respawn()
					} else {
						// Mix of reads and writes during respawn
						switch j % 4 {
						case 0:
							_ = gs.GetNumberHarvests()
						case 1:
							gs.SetNumberHarvests(int8(goroutineID % 3))
						case 2:
							_ = gs.IsAvailable()
						case 3:
							_ = gs.IsDepleted()
						}
					}
				}
			}(i)
		}

		wg.Wait()
	})
}

// Stress test Manager with concurrent operations
func TestManagerConcurrency(t *testing.T) {
	manager := NewManager(nil, &mockLogger{})

	// Pre-populate with some ground spawns
	for i := int32(1); i <= 10; i++ {
		config := GroundSpawnConfig{
			GroundSpawnID:      i,
			CollectionSkill:    SkillGathering,
			NumberHarvests:     5,
			AttemptsPerHarvest: 1,
			Location: SpawnLocation{
				X: float32(i * 10), Y: float32(i * 20), Z: float32(i * 30),
				Heading: float32(i * 45), GridID: 1,
			},
			Name:        "Test Node",
			Description: "Test node",
		}
		gs := manager.CreateGroundSpawn(config)
		if gs == nil {
			t.Fatalf("Failed to create ground spawn %d", i)
		}
	}

	const numGoroutines = 100
	const operationsPerGoroutine = 100

	var wg sync.WaitGroup

	t.Run("ConcurrentGroundSpawnAccess", func(t *testing.T) {
		wg.Add(numGoroutines)

		for i := range numGoroutines {
			go func(goroutineID int) {
				defer wg.Done()

				for j := range operationsPerGoroutine {
					spawnID := int32((goroutineID % 10) + 1)

					switch j % 5 {
					case 0:
						_ = manager.GetGroundSpawn(spawnID)
					case 1:
						_ = manager.GetGroundSpawnsByZone(1)
					case 2:
						_ = manager.GetGroundSpawnCount()
					case 3:
						_ = manager.GetActiveGroundSpawns()
					case 4:
						_ = manager.GetDepletedGroundSpawns()
					}
				}
			}(i)
		}

		wg.Wait()
	})

	t.Run("ConcurrentStatisticsOperations", func(t *testing.T) {
		wg.Add(numGoroutines)

		for i := range numGoroutines {
			go func(goroutineID int) {
				defer wg.Done()

				for j := range operationsPerGoroutine {
					switch j % 3 {
					case 0:
						_ = manager.GetStatistics()
					case 1:
						manager.ResetStatistics()
					case 2:
						// Simulate harvest statistics updates
						manager.mutex.Lock()
						manager.totalHarvests++
						manager.successfulHarvests++
						skill := SkillGathering
						manager.harvestsBySkill[skill]++
						manager.mutex.Unlock()
					}
				}
			}(i)
		}

		wg.Wait()

		// Verify statistics consistency
		stats := manager.GetStatistics()
		if stats.TotalHarvests < 0 || stats.SuccessfulHarvests < 0 {
			t.Errorf("Invalid statistics: total=%d, successful=%d",
				stats.TotalHarvests, stats.SuccessfulHarvests)
		}
	})

	t.Run("ConcurrentGroundSpawnModification", func(t *testing.T) {
		wg.Add(numGoroutines)

		for i := range numGoroutines {
			go func(goroutineID int) {
				defer wg.Done()

				for j := range operationsPerGoroutine {
					// Use a more unique ID generation strategy to avoid conflicts
					// Start at 10000 and use goroutine*1000 + iteration to ensure uniqueness
					newID := int32(10000 + goroutineID*1000 + j)

					config := GroundSpawnConfig{
						GroundSpawnID:      newID,
						CollectionSkill:    SkillMining,
						NumberHarvests:     3,
						AttemptsPerHarvest: 1,
						Location: SpawnLocation{
							X: float32(j), Y: float32(j * 2), Z: float32(j * 3),
							Heading: float32(j * 10), GridID: 1,
						},
						Name:        "Concurrent Node",
						Description: "Concurrent test node",
					}

					// Add ground spawn - note that CreateGroundSpawn overwrites the ID
					gs := manager.CreateGroundSpawn(config)
					if gs == nil {
						t.Errorf("Goroutine %d: Failed to create ground spawn", goroutineID)
						continue
					}

					// Since CreateGroundSpawn assigns its own ID, we need to get the actual ID
					actualID := gs.GetID()

					// Verify it was added with the manager-assigned ID
					retrieved := manager.GetGroundSpawn(actualID)
					if retrieved == nil {
						t.Errorf("Goroutine %d: Failed to retrieve ground spawn %d", goroutineID, actualID)
					}
				}
			}(i)
		}

		wg.Wait()
	})

	t.Run("ConcurrentRespawnProcessing", func(t *testing.T) {
		wg.Add(numGoroutines)

		for i := range numGoroutines {
			go func(goroutineID int) {
				defer wg.Done()

				for j := range operationsPerGoroutine {
					if j%50 == 0 {
						// Process respawns occasionally
						manager.ProcessRespawns()
					} else {
						// Schedule respawns
						spawnID := int32((goroutineID % 10) + 1)
						if gs := manager.GetGroundSpawn(spawnID); gs != nil {
							if gs.IsDepleted() {
								manager.scheduleRespawn(gs)
							}
						}
					}
				}
			}(i)
		}

		wg.Wait()
	})
}

// Test for potential deadlocks
func TestDeadlockPrevention(t *testing.T) {
	manager := NewManager(nil, &mockLogger{})

	// Create test ground spawns
	for i := int32(1); i <= 10; i++ {
		config := GroundSpawnConfig{
			GroundSpawnID:      i,
			CollectionSkill:    SkillGathering,
			NumberHarvests:     5,
			AttemptsPerHarvest: 1,
			Location: SpawnLocation{
				X: float32(i * 10), Y: float32(i * 20), Z: float32(i * 30),
				Heading: 0, GridID: 1,
			},
			Name:        "Deadlock Test Node",
			Description: "Test node",
		}
		gs := manager.CreateGroundSpawn(config)
		if gs == nil {
			t.Fatalf("Failed to create ground spawn %d", i)
		}
	}

	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")
			}
		}()

		wg.Add(numGoroutines)

		for i := range numGoroutines {
			go func(goroutineID int) {
				defer wg.Done()

				for j := range 100 {
					spawnID := int32((goroutineID % 10) + 1)

					// Mix operations that could potentially deadlock
					switch j % 8 {
					case 0:
						gs := manager.GetGroundSpawn(spawnID)
						if gs != nil {
							_ = gs.GetNumberHarvests()
						}
					case 1:
						_ = manager.GetStatistics()
					case 2:
						_ = manager.GetGroundSpawnsByZone(1)
					case 3:
						gs := manager.GetGroundSpawn(spawnID)
						if gs != nil {
							gs.SetNumberHarvests(int8(j % 5))
						}
					case 4:
						manager.ProcessRespawns()
					case 5:
						_ = manager.GetActiveGroundSpawns()
					case 6:
						gs := manager.GetGroundSpawn(spawnID)
						if gs != nil {
							_ = gs.Copy()
						}
					case 7:
						gs := manager.GetGroundSpawn(spawnID)
						if gs != nil && gs.IsDepleted() {
							manager.scheduleRespawn(gs)
						}
					}
				}
			}(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")
	}

	manager := NewManager(nil, &mockLogger{})

	// Rapid concurrent operations to trigger race conditions
	const numGoroutines = 200
	const operationsPerGoroutine = 50

	var wg sync.WaitGroup
	wg.Add(numGoroutines)

	for i := range numGoroutines {
		go func(goroutineID int) {
			defer wg.Done()

			for j := range operationsPerGoroutine {
				// Use unique IDs to avoid conflicts in rapid creation
				uniqueID := int32(20000 + goroutineID*1000 + j)

				// Rapid-fire operations
				config := GroundSpawnConfig{
					GroundSpawnID:      uniqueID,
					CollectionSkill:    SkillGathering,
					NumberHarvests:     3,
					AttemptsPerHarvest: 1,
					Location: SpawnLocation{
						X: float32(j), Y: float32(j * 2), Z: float32(j * 3),
						Heading: 0, GridID: 1,
					},
					Name:        "Race Test Node",
					Description: "Race test",
				}

				gs := manager.CreateGroundSpawn(config)
				if gs != nil {
					actualID := gs.GetID() // Get the manager-assigned ID
					gs.SetNumberHarvests(int8(j%5 + 1))
					_ = gs.GetNumberHarvests()
					_ = gs.IsAvailable()
					copy := gs.Copy()
					if copy != nil {
						copy.SetCollectionSkill(SkillMining)
					}

					_ = manager.GetGroundSpawn(actualID)
				}

				_ = manager.GetStatistics()
				manager.ProcessRespawns()
			}
		}(i)
	}

	wg.Wait()
}

// Specific test for Copy() method mutex safety
func TestCopyMutexSafety(t *testing.T) {
	config := GroundSpawnConfig{
		GroundSpawnID:      1,
		CollectionSkill:    SkillGathering,
		NumberHarvests:     5,
		AttemptsPerHarvest: 1,
		Location: SpawnLocation{
			X: 100, Y: 200, Z: 300, Heading: 45, GridID: 1,
		},
		Name:        "Copy Test Node",
		Description: "Test node for copy safety",
	}

	original := NewGroundSpawn(config)

	const numGoroutines = 100
	var wg sync.WaitGroup
	wg.Add(numGoroutines)

	// Test copying while modifying
	for i := range numGoroutines {
		go func(goroutineID int) {
			defer wg.Done()

			for j := range 100 {
				if j%2 == 0 {
					// Copy operations
					copy := original.Copy()
					if copy == nil {
						t.Errorf("Goroutine %d: Copy returned nil", goroutineID)
						continue
					}

					// Verify copy is independent by setting a unique value
					expectedValue := int8(goroutineID%5 + 1) // Ensure non-zero value
					copy.SetNumberHarvests(expectedValue)

					// Verify the copy has the value we set
					if copy.GetNumberHarvests() != expectedValue {
						t.Errorf("Goroutine %d: Copy failed to set value correctly, expected %d got %d",
							goroutineID, expectedValue, copy.GetNumberHarvests())
					}

					// Copy independence is verified by the fact that we can set different values
					// We don't check against original since other goroutines are modifying it concurrently
				} else {
					// Modify original
					original.SetNumberHarvests(int8(goroutineID % 10))
					original.SetCollectionSkill(SkillMining)
					_ = original.GetRandomizeHeading()
				}
			}
		}(i)
	}

	wg.Wait()
}
