package traits

import (
	"fmt"
	"testing"
)

func TestTraitData(t *testing.T) {
	trait := &TraitData{
		SpellID:       12345,
		Level:         10,
		ClassReq:      UniversalClassReq,
		RaceReq:       UniversalRaceReq,
		IsTrait:       true,
		IsInnate:      false,
		IsFocusEffect: false,
		IsTraining:    false,
		Tier:          1,
		Group:         TraitsCombat,
		ItemID:        0,
	}

	// Test Copy method
	copied := trait.Copy()
	if copied == nil {
		t.Fatal("Copy returned nil")
	}

	if copied.SpellID != trait.SpellID {
		t.Errorf("Expected SpellID %d, got %d", trait.SpellID, copied.SpellID)
	}

	if copied.Level != trait.Level {
		t.Errorf("Expected Level %d, got %d", trait.Level, copied.Level)
	}

	// Test Copy with nil
	var nilTrait *TraitData
	copiedNil := nilTrait.Copy()
	if copiedNil != nil {
		t.Error("Copy of nil should return nil")
	}

	// Test IsUniversalTrait
	if !trait.IsUniversalTrait() {
		t.Error("Trait should be universal")
	}

	// Test IsForClass
	if !trait.IsForClass(5) {
		t.Error("Universal trait should be available for any class")
	}

	// Test IsForRace
	if !trait.IsForRace(3) {
		t.Error("Universal trait should be available for any race")
	}

	// Test GetTraitType
	traitType := trait.GetTraitType()
	if traitType != "Character Trait" {
		t.Errorf("Expected 'Character Trait', got '%s'", traitType)
	}

	// Test Validate
	err := trait.Validate()
	if err != nil {
		t.Errorf("Valid trait should pass validation: %v", err)
	}

	// Test invalid trait
	invalidTrait := &TraitData{
		SpellID: 0,  // Invalid
		Level:   -1, // Invalid
		Group:   10, // Invalid
	}

	err = invalidTrait.Validate()
	if err == nil {
		t.Error("Invalid trait should fail validation")
	}
}

func TestMasterTraitList(t *testing.T) {
	masterList := NewMasterTraitList()
	if masterList == nil {
		t.Fatal("NewMasterTraitList returned nil")
	}

	// Test initial state
	if masterList.Size() != 0 {
		t.Error("New master list should be empty")
	}

	// Test AddTrait
	trait := &TraitData{
		SpellID:       12345,
		Level:         10,
		ClassReq:      UniversalClassReq,
		RaceReq:       UniversalRaceReq,
		IsTrait:       true,
		IsInnate:      false,
		IsFocusEffect: false,
		IsTraining:    false,
		Tier:          1,
		Group:         TraitsCombat,
		ItemID:        67890,
	}

	err := masterList.AddTrait(trait)
	if err != nil {
		t.Fatalf("AddTrait failed: %v", err)
	}

	if masterList.Size() != 1 {
		t.Errorf("Expected size 1 after adding trait, got %d", masterList.Size())
	}

	// Test GetTrait
	retrieved := masterList.GetTrait(12345)
	if retrieved == nil {
		t.Fatal("GetTrait returned nil")
	}

	if retrieved.SpellID != 12345 {
		t.Errorf("Expected SpellID 12345, got %d", retrieved.SpellID)
	}

	// Test GetTraitByItemID
	retrievedByItem := masterList.GetTraitByItemID(67890)
	if retrievedByItem == nil {
		t.Fatal("GetTraitByItemID returned nil")
	}

	if retrievedByItem.ItemID != 67890 {
		t.Errorf("Expected ItemID 67890, got %d", retrievedByItem.ItemID)
	}

	// Test GetTrait with non-existent ID
	nonExistent := masterList.GetTrait(99999)
	if nonExistent != nil {
		t.Error("GetTrait should return nil for non-existent trait")
	}

	// Test AddTrait with nil
	err = masterList.AddTrait(nil)
	if err == nil {
		t.Error("AddTrait should fail with nil trait")
	}

	// Test DestroyTraits
	masterList.DestroyTraits()
	if masterList.Size() != 0 {
		t.Error("Size should be 0 after DestroyTraits")
	}
}

func TestPlayerTraitState(t *testing.T) {
	playerState := NewPlayerTraitState(12345, 25, 1, 2)
	if playerState == nil {
		t.Fatal("NewPlayerTraitState returned nil")
	}

	if playerState.PlayerID != 12345 {
		t.Errorf("Expected PlayerID 12345, got %d", playerState.PlayerID)
	}

	// Test UpdateLevel
	playerState.UpdateLevel(30)
	if playerState.Level != 30 {
		t.Errorf("Expected level 30, got %d", playerState.Level)
	}

	if !playerState.NeedTraitUpdate {
		t.Error("Should need trait update after level change")
	}

	// Test trait selection
	playerState.SelectTrait(11111)
	if !playerState.HasTrait(11111) {
		t.Error("Player should have selected trait")
	}

	if playerState.GetSelectedTraitCount() != 1 {
		t.Errorf("Expected 1 selected trait, got %d", playerState.GetSelectedTraitCount())
	}

	// Test trait unselection
	playerState.UnselectTrait(11111)
	if playerState.HasTrait(11111) {
		t.Error("Player should not have unselected trait")
	}

	if playerState.GetSelectedTraitCount() != 0 {
		t.Errorf("Expected 0 selected traits, got %d", playerState.GetSelectedTraitCount())
	}
}

func TestTraitLists(t *testing.T) {
	traitLists := NewTraitLists()
	if traitLists == nil {
		t.Fatal("NewTraitLists returned nil")
	}

	// Test initial state
	if len(traitLists.SortedTraitList) != 0 {
		t.Error("SortedTraitList should be empty initially")
	}

	// Test Clear
	traitLists.SortedTraitList[0] = make(map[int8][]*TraitData)
	traitLists.Clear()

	if len(traitLists.SortedTraitList) != 0 {
		t.Error("SortedTraitList should be empty after Clear")
	}
}

func TestTraitSelectionContext(t *testing.T) {
	context := NewTraitSelectionContext(true)
	if context == nil {
		t.Fatal("NewTraitSelectionContext returned nil")
	}

	if !context.TieredSelection {
		t.Error("TieredSelection should be true")
	}

	if context.GroupToApply != UnassignedGroupID {
		t.Error("GroupToApply should be UnassignedGroupID initially")
	}

	// Test Reset
	context.GroupToApply = 5
	context.FoundSpellMatch = true
	context.Reset()

	if context.GroupToApply != UnassignedGroupID {
		t.Error("GroupToApply should be reset to UnassignedGroupID")
	}

	if context.FoundSpellMatch {
		t.Error("FoundSpellMatch should be reset to false")
	}
}

func TestTraitSystemConfig(t *testing.T) {
	config := &TraitSystemConfig{
		TieringSelection:     true,
		UseClassicLevelTable: false,
		FocusSelectLevel:     9,
		TrainingSelectLevel:  10,
		RaceSelectLevel:      10,
		CharacterSelectLevel: 4,
	}

	if !config.TieringSelection {
		t.Error("TieringSelection should be true")
	}

	if config.FocusSelectLevel != 9 {
		t.Errorf("Expected FocusSelectLevel 9, got %d", config.FocusSelectLevel)
	}
}

func TestGenerateTraitLists(t *testing.T) {
	masterList := NewMasterTraitList()

	// Add test traits
	traits := []*TraitData{
		{
			SpellID:  1001,
			Level:    10,
			ClassReq: UniversalClassReq,
			RaceReq:  UniversalRaceReq,
			IsTrait:  true,
			Group:    TraitsCombat,
		},
		{
			SpellID:    1002,
			Level:      15,
			ClassReq:   5, // Specific class
			IsTraining: true,
			Group:      TraitsAttributes,
		},
		{
			SpellID: 1003,
			Level:   20,
			RaceReq: 3, // Specific race
			Group:   TraitsNoncombat,
		},
		{
			SpellID:  1004,
			Level:    25,
			RaceReq:  3,
			IsInnate: true,
			Group:    TraitsPools,
		},
		{
			SpellID:       1005,
			Level:         30,
			ClassReq:      5,
			IsFocusEffect: true,
			Group:         TraitsResist,
		},
	}

	for _, trait := range traits {
		masterList.AddTrait(trait)
	}

	playerState := NewPlayerTraitState(12345, 50, 5, 3)

	// Test GenerateTraitLists
	success := masterList.GenerateTraitLists(playerState, 50, UnassignedGroupID)
	if !success {
		t.Fatal("GenerateTraitLists should succeed")
	}

	// Check that traits were categorized correctly

	// Should have 1 character trait (universal)
	characterTraitFound := false
	for _, levelMap := range playerState.TraitLists.SortedTraitList {
		if len(levelMap) > 0 {
			characterTraitFound = true
			break
		}
	}
	if !characterTraitFound {
		t.Error("Should have character traits")
	}

	// Should have 1 class training trait
	if len(playerState.TraitLists.ClassTraining) == 0 {
		t.Error("Should have class training traits")
	}

	// Should have 1 racial trait
	if len(playerState.TraitLists.RaceTraits) == 0 {
		t.Error("Should have racial traits")
	}

	// Should have 1 innate racial trait
	if len(playerState.TraitLists.InnateRaceTraits) == 0 {
		t.Error("Should have innate racial traits")
	}

	// Should have 1 focus effect
	if len(playerState.TraitLists.FocusEffects) == 0 {
		t.Error("Should have focus effects")
	}
}

func TestIsPlayerAllowedTrait(t *testing.T) {
	masterList := NewMasterTraitList()
	playerState := NewPlayerTraitState(12345, 20, 5, 3)

	config := &TraitSystemConfig{
		TieringSelection:     false,
		UseClassicLevelTable: false,
		FocusSelectLevel:     9,
		TrainingSelectLevel:  10,
		RaceSelectLevel:      10,
		CharacterSelectLevel: 4,
	}

	trait := &TraitData{
		SpellID:  1001,
		Level:    10,
		ClassReq: UniversalClassReq,
		RaceReq:  UniversalRaceReq,
		IsTrait:  true,
		Group:    TraitsCombat,
	}

	masterList.AddTrait(trait)

	// Generate trait lists
	masterList.GenerateTraitLists(playerState, 50, UnassignedGroupID)

	// Test trait allowance
	allowed := masterList.IsPlayerAllowedTrait(playerState, trait, config)
	if !allowed {
		t.Error("Player should be allowed this trait")
	}
}

func TestTraitManager(t *testing.T) {
	masterList := NewMasterTraitList()
	config := &TraitSystemConfig{
		TieringSelection:     false,
		UseClassicLevelTable: false,
		FocusSelectLevel:     9,
		TrainingSelectLevel:  10,
		RaceSelectLevel:      10,
		CharacterSelectLevel: 4,
	}

	manager := NewTraitManager(masterList, config)
	if manager == nil {
		t.Fatal("NewTraitManager returned nil")
	}

	// Test GetPlayerState
	playerState := manager.GetPlayerState(12345, 25, 5, 3)
	if playerState == nil {
		t.Fatal("GetPlayerState returned nil")
	}

	if playerState.PlayerID != 12345 {
		t.Errorf("Expected PlayerID 12345, got %d", playerState.PlayerID)
	}

	// Test level update
	playerState2 := manager.GetPlayerState(12345, 30, 5, 3)
	if playerState2.Level != 30 {
		t.Errorf("Expected level 30, got %d", playerState2.Level)
	}

	// Test ClearPlayerState
	manager.ClearPlayerState(12345)

	// Should create new state after clearing
	playerState3 := manager.GetPlayerState(12345, 25, 5, 3)
	if playerState3 == playerState {
		t.Error("Should create new state after clearing")
	}
}

func TestTraitPacketHelper(t *testing.T) {
	helper := NewTraitPacketHelper()
	if helper == nil {
		t.Fatal("NewTraitPacketHelper returned nil")
	}

	// Test FormatTraitFieldName
	fieldName := helper.FormatTraitFieldName("trait", 2, "_icon")
	if fieldName != "trait2_icon" {
		t.Errorf("Expected 'trait2_icon', got '%s'", fieldName)
	}

	// Test GetPacketTypeForTrait
	trait := &TraitData{
		ClassReq:   UniversalClassReq,
		RaceReq:    UniversalRaceReq,
		IsTrait:    true,
		IsTraining: false,
	}

	packetType := helper.GetPacketTypeForTrait(trait, 5, 3)
	if packetType != PacketTypeCharacterTrait {
		t.Errorf("Expected PacketTypeCharacterTrait (%d), got %d", PacketTypeCharacterTrait, packetType)
	}

	// Test CalculateAvailableSelections
	available := helper.CalculateAvailableSelections(30, 2, 10)
	if available != 1 {
		t.Errorf("Expected 1 available selection, got %d", available)
	}

	// Test GetClassicLevelRequirement
	levelReq := helper.GetClassicLevelRequirement(PersonalTraitLevelLimits, 2)
	if levelReq != PersonalTraitLevelLimits[2] {
		t.Errorf("Expected %d, got %d", PersonalTraitLevelLimits[2], levelReq)
	}

	// Test BuildEmptyTraitSlot
	emptySlot := helper.BuildEmptyTraitSlot()
	if emptySlot.SpellID != EmptyTraitID {
		t.Errorf("Expected EmptyTraitID (%d), got %d", EmptyTraitID, emptySlot.SpellID)
	}

	// Test CountSelectedTraits
	traits := []TraitInfo{
		{Selected: true},
		{Selected: false},
		{Selected: true},
	}

	count := helper.CountSelectedTraits(traits)
	if count != 2 {
		t.Errorf("Expected 2 selected traits, got %d", count)
	}
}

func TestTraitErrors(t *testing.T) {
	// Test TraitError
	err := NewTraitError("test error")
	if err == nil {
		t.Fatal("NewTraitError returned nil")
	}

	if err.Error() != "test error" {
		t.Errorf("Expected 'test error', got '%s'", err.Error())
	}

	// Test IsTraitError
	if !IsTraitError(err) {
		t.Error("Should identify as trait error")
	}

	// Test with non-trait error
	if IsTraitError(fmt.Errorf("not a trait error")) {
		t.Error("Should not identify as trait error")
	}
}

func TestConstants(t *testing.T) {
	// Test trait group constants
	if TraitsAttributes != 0 {
		t.Errorf("Expected TraitsAttributes to be 0, got %d", TraitsAttributes)
	}

	if TraitsTradeskill != 5 {
		t.Errorf("Expected TraitsTradeskill to be 5, got %d", TraitsTradeskill)
	}

	// Test level limits
	if len(PersonalTraitLevelLimits) != 9 {
		t.Errorf("Expected 9 personal trait level limits, got %d", len(PersonalTraitLevelLimits))
	}

	if PersonalTraitLevelLimits[1] != 8 {
		t.Errorf("Expected first personal trait at level 8, got %d", PersonalTraitLevelLimits[1])
	}

	// Test trait group names
	if TraitGroupNames[TraitsAttributes] != "Attributes" {
		t.Errorf("Expected 'Attributes', got '%s'", TraitGroupNames[TraitsAttributes])
	}

	// Test constants
	if MaxTraitsPerLine != 5 {
		t.Errorf("Expected MaxTraitsPerLine to be 5, got %d", MaxTraitsPerLine)
	}

	if UniversalClassReq != -1 {
		t.Errorf("Expected UniversalClassReq to be -1, got %d", UniversalClassReq)
	}
}

func BenchmarkMasterTraitListAccess(b *testing.B) {
	masterList := NewMasterTraitList()

	// Add test traits
	for i := 0; i < 1000; i++ {
		trait := &TraitData{
			SpellID: uint32(i + 1000),
			Level:   int8((i % 50) + 1),
			Group:   int8(i % 6),
		}
		masterList.AddTrait(trait)
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		masterList.GetTrait(uint32((i % 1000) + 1000))
	}
}

func BenchmarkGenerateTraitLists(b *testing.B) {
	masterList := NewMasterTraitList()

	// Add test traits
	for i := 0; i < 100; i++ {
		trait := &TraitData{
			SpellID:  uint32(i + 1000),
			Level:    int8((i % 50) + 1),
			ClassReq: UniversalClassReq,
			RaceReq:  UniversalRaceReq,
			IsTrait:  true,
			Group:    int8(i % 6),
		}
		masterList.AddTrait(trait)
	}

	playerState := NewPlayerTraitState(12345, 50, 5, 3)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		masterList.GenerateTraitLists(playerState, 50, UnassignedGroupID)
	}
}
