package traits

import (
	"fmt"
	"log"
	"strconv"
)

// TraitPacketBuilder handles building trait-related packets for client communication.
type TraitPacketBuilder interface {
	// BuildTraitListPacket builds the main trait list packet (WS_TraitsList)
	BuildTraitListPacket(playerState *PlayerTraitState, clientVersion int16) (*TraitPacketData, error)

	// BuildTraitRewardPacket builds a trait reward selection packet (WS_QuestRewardPackMsg)
	BuildTraitRewardPacket(traits []*TraitData, packetType int8) (*TraitRewardPacket, error)
}

// TraitRewardPacket represents a trait reward selection packet.
type TraitRewardPacket struct {
	PacketType    int8              // Type of trait packet (0-3)
	SelectRewards []TraitRewardItem // Available trait selections
	ItemRewards   []TraitRewardItem // Associated item rewards
}

// TraitRewardItem represents a reward item in trait selection.
type TraitRewardItem struct {
	SpellID uint32 // Spell ID for the trait
	ItemID  uint32 // Associated item ID (if any)
	Name    string // Display name
	Icon    uint16 // Icon for display
}

// DefaultTraitPacketBuilder is the default implementation of TraitPacketBuilder.
type DefaultTraitPacketBuilder struct {
	spellManager SpellManager // Interface to spell system
	itemManager  ItemManager  // Interface to item system
}

// NewDefaultTraitPacketBuilder creates a new default trait packet builder.
func NewDefaultTraitPacketBuilder(spellMgr SpellManager, itemMgr ItemManager) *DefaultTraitPacketBuilder {
	return &DefaultTraitPacketBuilder{
		spellManager: spellMgr,
		itemManager:  itemMgr,
	}
}

// BuildTraitListPacket builds the comprehensive trait list packet for a player.
func (pb *DefaultTraitPacketBuilder) BuildTraitListPacket(playerState *PlayerTraitState, clientVersion int16) (*TraitPacketData, error) {
	if playerState == nil {
		return nil, ErrInvalidPlayer
	}

	packetData := &TraitPacketData{}

	// Build character traits section
	err := pb.buildCharacterTraits(playerState, packetData)
	if err != nil {
		return nil, fmt.Errorf("failed to build character traits: %w", err)
	}

	// Build class training section
	err = pb.buildClassTraining(playerState, packetData)
	if err != nil {
		return nil, fmt.Errorf("failed to build class training: %w", err)
	}

	// Build racial traits section
	err = pb.buildRacialTraits(playerState, packetData)
	if err != nil {
		return nil, fmt.Errorf("failed to build racial traits: %w", err)
	}

	// Build innate abilities section
	err = pb.buildInnateAbilities(playerState, packetData)
	if err != nil {
		return nil, fmt.Errorf("failed to build innate abilities: %w", err)
	}

	// Build focus effects section (for supported client versions)
	if clientVersion >= FocusEffectsMinVersion {
		err = pb.buildFocusEffects(playerState, packetData)
		if err != nil {
			return nil, fmt.Errorf("failed to build focus effects: %w", err)
		}
	}

	// Calculate selection availability
	pb.calculateSelectionAvailability(playerState, packetData)

	return packetData, nil
}

// buildCharacterTraits builds the character traits section of the packet.
func (pb *DefaultTraitPacketBuilder) buildCharacterTraits(playerState *PlayerTraitState, packetData *TraitPacketData) error {
	traitLevels := make([]TraitLevelData, 0)

	// Iterate through sorted trait list (group -> level -> traits)
	for _, levelMap := range playerState.TraitLists.SortedTraitList {
		for level, traits := range levelMap {
			if len(traits) == 0 {
				continue
			}

			levelData := TraitLevelData{
				Level:        level,
				SelectedLine: UnassignedGroupID, // Default to no selection
				Traits:       make([]TraitInfo, 0, MaxTraitsPerLine),
			}

			// Add up to MaxTraitsPerLine traits
			for i, trait := range traits {
				if i >= MaxTraitsPerLine {
					break
				}

				traitInfo, err := pb.buildTraitInfo(trait, playerState)
				if err != nil {
					log.Printf("Warning: Failed to build trait info for spell %d: %v", trait.SpellID, err)
					continue
				}

				levelData.Traits = append(levelData.Traits, *traitInfo)

				// Check if this trait is selected
				if playerState.HasTrait(trait.SpellID) {
					levelData.SelectedLine = int8(i)
				}
			}

			// Fill remaining slots with empty entries
			for len(levelData.Traits) < MaxTraitsPerLine {
				levelData.Traits = append(levelData.Traits, TraitInfo{
					SpellID:  EmptyTraitID,
					Name:     "",
					Icon:     EmptyTraitIcon,
					Icon2:    EmptyTraitIcon,
					Selected: false,
					Unknown1: EmptyTraitUnknown,
					Unknown2: EmptyTraitUnknown,
				})
			}

			traitLevels = append(traitLevels, levelData)
		}
	}

	packetData.CharacterTraits = traitLevels
	return nil
}

// buildClassTraining builds the class training section of the packet.
func (pb *DefaultTraitPacketBuilder) buildClassTraining(playerState *PlayerTraitState, packetData *TraitPacketData) error {
	trainingLevels := make([]TraitLevelData, 0)

	for level, traits := range playerState.TraitLists.ClassTraining {
		if len(traits) == 0 {
			continue
		}

		levelData := TraitLevelData{
			Level:        level,
			SelectedLine: UnassignedGroupID,
			Traits:       make([]TraitInfo, 0, MaxTraitsPerLine),
		}

		// Add up to MaxTraitsPerLine traits
		for i, trait := range traits {
			if i >= MaxTraitsPerLine {
				break
			}

			traitInfo, err := pb.buildTraitInfo(trait, playerState)
			if err != nil {
				log.Printf("Warning: Failed to build training info for spell %d: %v", trait.SpellID, err)
				continue
			}

			levelData.Traits = append(levelData.Traits, *traitInfo)

			if playerState.HasTrait(trait.SpellID) {
				levelData.SelectedLine = int8(i)
			}
		}

		// Fill remaining slots with empty entries
		for len(levelData.Traits) < MaxTraitsPerLine {
			levelData.Traits = append(levelData.Traits, TraitInfo{
				SpellID:  EmptyTraitID,
				Name:     "",
				Icon:     EmptyTraitIcon,
				Icon2:    EmptyTraitIcon,
				Selected: false,
				Unknown1: EmptyTraitUnknown,
				Unknown2: EmptyTraitUnknown,
			})
		}

		trainingLevels = append(trainingLevels, levelData)
	}

	packetData.ClassTraining = trainingLevels
	return nil
}

// buildRacialTraits builds the racial traits section of the packet.
func (pb *DefaultTraitPacketBuilder) buildRacialTraits(playerState *PlayerTraitState, packetData *TraitPacketData) error {
	racialGroups := make([]RacialTraitGroup, 0)

	for groupID, traits := range playerState.TraitLists.RaceTraits {
		if len(traits) == 0 {
			continue
		}

		groupName := TraitGroupNames[groupID]
		if groupName == "" {
			groupName = "Unknown"
		}

		group := RacialTraitGroup{
			GroupName: groupName,
			Traits:    make([]TraitInfo, 0),
		}

		for _, trait := range traits {
			traitInfo, err := pb.buildTraitInfo(trait, playerState)
			if err != nil {
				log.Printf("Warning: Failed to build racial trait info for spell %d: %v", trait.SpellID, err)
				continue
			}

			group.Traits = append(group.Traits, *traitInfo)
		}

		racialGroups = append(racialGroups, group)
	}

	packetData.RacialTraits = racialGroups
	return nil
}

// buildInnateAbilities builds the innate abilities section of the packet.
func (pb *DefaultTraitPacketBuilder) buildInnateAbilities(playerState *PlayerTraitState, packetData *TraitPacketData) error {
	innateAbilities := make([]TraitInfo, 0)

	for _, traits := range playerState.TraitLists.InnateRaceTraits {
		for _, trait := range traits {
			traitInfo, err := pb.buildTraitInfo(trait, playerState)
			if err != nil {
				log.Printf("Warning: Failed to build innate ability info for spell %d: %v", trait.SpellID, err)
				continue
			}

			innateAbilities = append(innateAbilities, *traitInfo)
		}
	}

	packetData.InnateAbilities = innateAbilities
	return nil
}

// buildFocusEffects builds the focus effects section of the packet.
func (pb *DefaultTraitPacketBuilder) buildFocusEffects(playerState *PlayerTraitState, packetData *TraitPacketData) error {
	focusEffects := make([]TraitInfo, 0)

	for _, traits := range playerState.TraitLists.FocusEffects {
		for _, trait := range traits {
			traitInfo, err := pb.buildTraitInfo(trait, playerState)
			if err != nil {
				log.Printf("Warning: Failed to build focus effect info for spell %d: %v", trait.SpellID, err)
				continue
			}

			focusEffects = append(focusEffects, *traitInfo)
		}
	}

	packetData.FocusEffects = focusEffects
	return nil
}

// buildTraitInfo creates a TraitInfo structure for a specific trait.
func (pb *DefaultTraitPacketBuilder) buildTraitInfo(trait *TraitData, playerState *PlayerTraitState) (*TraitInfo, error) {
	if trait == nil {
		return nil, fmt.Errorf("trait is nil")
	}

	// Get spell information
	spell, err := pb.spellManager.GetSpell(trait.SpellID, trait.Tier)
	if err != nil {
		return nil, fmt.Errorf("failed to get spell %d tier %d: %w", trait.SpellID, trait.Tier, err)
	}

	traitInfo := &TraitInfo{
		SpellID:  trait.SpellID,
		Name:     spell.GetName(),
		Icon:     uint16(spell.GetIcon()),
		Icon2:    uint16(spell.GetIconBackdrop()),
		Selected: playerState.HasTrait(trait.SpellID),
		Unknown1: DefaultUnknownField1,
		Unknown2: DefaultUnknownField2,
	}

	return traitInfo, nil
}

// calculateSelectionAvailability calculates how many trait selections are available.
func (pb *DefaultTraitPacketBuilder) calculateSelectionAvailability(playerState *PlayerTraitState, packetData *TraitPacketData) {
	// Calculate racial trait selections
	racialSelectionsUsed := int8(0)
	for _, group := range packetData.RacialTraits {
		for _, trait := range group.Traits {
			if trait.Selected {
				racialSelectionsUsed++
			}
		}
	}

	racialSelectionsAvailable := playerState.Level / 10 // Every 10 levels
	if racialSelectionsUsed < int8(racialSelectionsAvailable) {
		packetData.RacialSelectionsAvailable = int8(racialSelectionsAvailable) - racialSelectionsUsed
	} else {
		packetData.RacialSelectionsAvailable = 0
	}

	// Calculate focus effect selections
	focusSelectionsUsed := int8(0)
	for _, trait := range packetData.FocusEffects {
		if trait.Selected {
			focusSelectionsUsed++
		}
	}

	focusSelectionsAvailable := playerState.Level / 9 // Every 9 levels
	if focusSelectionsUsed < int8(focusSelectionsAvailable) {
		packetData.FocusSelectionsAvailable = int8(focusSelectionsAvailable) - focusSelectionsUsed
	} else {
		packetData.FocusSelectionsAvailable = 0
	}
}

// BuildTraitRewardPacket builds a trait reward selection packet.
func (pb *DefaultTraitPacketBuilder) BuildTraitRewardPacket(traits []*TraitData, packetType int8) (*TraitRewardPacket, error) {
	if len(traits) == 0 {
		return nil, fmt.Errorf("no traits provided")
	}

	packet := &TraitRewardPacket{
		PacketType:    packetType,
		SelectRewards: make([]TraitRewardItem, 0),
		ItemRewards:   make([]TraitRewardItem, 0),
	}

	for _, trait := range traits {
		// Build reward item for trait selection
		rewardItem := TraitRewardItem{
			SpellID: trait.SpellID,
		}

		// Get spell information for display
		spell, err := pb.spellManager.GetSpell(trait.SpellID, trait.Tier)
		if err != nil {
			log.Printf("Warning: Failed to get spell %d for trait reward: %v", trait.SpellID, err)
			rewardItem.Name = fmt.Sprintf("Unknown Trait %d", trait.SpellID)
			rewardItem.Icon = 0
		} else {
			rewardItem.Name = spell.GetName()
			rewardItem.Icon = uint16(spell.GetIcon())
		}

		packet.SelectRewards = append(packet.SelectRewards, rewardItem)

		// Add associated item reward if trait has an item
		if trait.ItemID > 0 {
			itemReward := TraitRewardItem{
				SpellID: trait.SpellID,
				ItemID:  trait.ItemID,
			}

			item, err := pb.itemManager.GetItem(trait.ItemID)
			if err != nil {
				log.Printf("Warning: Failed to get item %d for trait reward: %v", trait.ItemID, err)
				itemReward.Name = fmt.Sprintf("Unknown Item %d", trait.ItemID)
				itemReward.Icon = 0
			} else {
				itemReward.Name = item.GetName()
				itemReward.Icon = uint16(item.GetIcon())
			}

			packet.ItemRewards = append(packet.ItemRewards, itemReward)
		}
	}

	return packet, nil
}

// TraitPacketHelper provides utility functions for trait packet building.
type TraitPacketHelper struct{}

// NewTraitPacketHelper creates a new trait packet helper.
func NewTraitPacketHelper() *TraitPacketHelper {
	return &TraitPacketHelper{}
}

// FormatTraitFieldName creates properly formatted field names for trait packets.
// This matches the C++ string building logic using sprintf and strcat.
func (ph *TraitPacketHelper) FormatTraitFieldName(baseField string, index int, suffix string) string {
	return baseField + strconv.Itoa(index) + suffix
}

// GetPacketTypeForTrait determines the appropriate packet type for a trait.
func (ph *TraitPacketHelper) GetPacketTypeForTrait(trait *TraitData, playerClass, playerRace int8) int8 {
	// Character Traits
	if trait.ClassReq == UniversalClassReq && trait.RaceReq == UniversalRaceReq && trait.IsTrait {
		return PacketTypeCharacterTrait
	}

	// Class Training
	if trait.ClassReq == playerClass && trait.IsTraining {
		return PacketTypeSpecializedTraining
	}

	// Racial Abilities (both innate and non-innate)
	if trait.RaceReq == playerRace && (!trait.IsTraining || trait.IsInnate) {
		return PacketTypeRacialTradition
	}

	// Default to enemy mastery
	return PacketTypeEnemyMastery
}

// ValidateTraitPacketData validates trait packet data before sending.
func (ph *TraitPacketHelper) ValidateTraitPacketData(packetData *TraitPacketData) error {
	if packetData == nil {
		return fmt.Errorf("packet data is nil")
	}

	// Validate character traits
	for i, levelData := range packetData.CharacterTraits {
		if len(levelData.Traits) > MaxTraitsPerLine {
			return fmt.Errorf("character trait level %d has too many traits: %d", i, len(levelData.Traits))
		}
	}

	// Validate class training
	for i, levelData := range packetData.ClassTraining {
		if len(levelData.Traits) > MaxTraitsPerLine {
			return fmt.Errorf("class training level %d has too many traits: %d", i, len(levelData.Traits))
		}
	}

	// Validate racial traits
	for i, group := range packetData.RacialTraits {
		if group.GroupName == "" {
			return fmt.Errorf("racial trait group %d has empty name", i)
		}
	}

	return nil
}

// CalculateAvailableSelections calculates available selections for different trait types.
func (ph *TraitPacketHelper) CalculateAvailableSelections(playerLevel int16, usedSelections int8, intervalLevel int32) int8 {
	if intervalLevel <= 0 {
		return 0
	}

	availableSelections := playerLevel / int16(intervalLevel)
	if usedSelections < int8(availableSelections) {
		return int8(availableSelections) - usedSelections
	}

	return 0
}

// GetClassicLevelRequirement gets the level requirement from classic trait tables.
func (ph *TraitPacketHelper) GetClassicLevelRequirement(levelLimits []int16, selectionIndex int) int16 {
	if selectionIndex >= 0 && selectionIndex < len(levelLimits) {
		return levelLimits[selectionIndex]
	}
	return 0
}

// BuildEmptyTraitSlot creates an empty trait slot for packet filling.
func (ph *TraitPacketHelper) BuildEmptyTraitSlot() TraitInfo {
	return TraitInfo{
		SpellID:  EmptyTraitID,
		Name:     "",
		Icon:     EmptyTraitIcon,
		Icon2:    EmptyTraitIcon,
		Selected: false,
		Unknown1: EmptyTraitUnknown,
		Unknown2: EmptyTraitUnknown,
	}
}

// CountSelectedTraits counts how many traits are selected in a trait list.
func (ph *TraitPacketHelper) CountSelectedTraits(traits []TraitInfo) int8 {
	count := int8(0)
	for _, trait := range traits {
		if trait.Selected {
			count++
		}
	}
	return count
}

// SortTraitsByLevel sorts traits by their level requirement.
func (ph *TraitPacketHelper) SortTraitsByLevel(traits []*TraitData) []*TraitData {
	// Simple bubble sort for trait level ordering
	sorted := make([]*TraitData, len(traits))
	copy(sorted, traits)

	for i := 0; i < len(sorted)-1; i++ {
		for j := 0; j < len(sorted)-i-1; j++ {
			if sorted[j].Level > sorted[j+1].Level {
				sorted[j], sorted[j+1] = sorted[j+1], sorted[j]
			}
		}
	}

	return sorted
}

// GetTraitDisplayName gets an appropriate display name for a trait type.
func (ph *TraitPacketHelper) GetTraitDisplayName(traitType int8) string {
	switch traitType {
	case PacketTypeEnemyMastery:
		return "Enemy Mastery"
	case PacketTypeSpecializedTraining:
		return "Specialized Training"
	case PacketTypeCharacterTrait:
		return "Character Trait"
	case PacketTypeRacialTradition:
		return "Racial Tradition"
	default:
		return "Unknown Trait Type"
	}
}
