package alt_advancement

import (
	"fmt"
	"sort"
	"time"
)

// NewMasterAAList creates a new master AA list
func NewMasterAAList() *MasterAAList {
	return &MasterAAList{
		aaList:       make([]*AltAdvanceData, 0),
		aaBySpellID:  make(map[int32]*AltAdvanceData),
		aaByNodeID:   make(map[int32]*AltAdvanceData),
		aaByGroup:    make(map[int8][]*AltAdvanceData),
		totalLoaded:  0,
		lastLoadTime: time.Now(),
	}
}

// AddAltAdvancement adds an AA to the master list
func (mal *MasterAAList) AddAltAdvancement(data *AltAdvanceData) error {
	if data == nil {
		return fmt.Errorf("data cannot be nil")
	}

	if !data.IsValid() {
		return fmt.Errorf("invalid AA data: spell_id=%d, node_id=%d", data.SpellID, data.NodeID)
	}

	mal.mutex.Lock()
	defer mal.mutex.Unlock()

	// Check for duplicates
	if _, exists := mal.aaBySpellID[data.SpellID]; exists {
		return fmt.Errorf("AA with spell ID %d already exists", data.SpellID)
	}

	if _, exists := mal.aaByNodeID[data.NodeID]; exists {
		return fmt.Errorf("AA with node ID %d already exists", data.NodeID)
	}

	// Add to main list
	mal.aaList = append(mal.aaList, data)

	// Add to lookup maps
	mal.aaBySpellID[data.SpellID] = data
	mal.aaByNodeID[data.NodeID] = data

	// Add to group map
	if mal.aaByGroup[data.Group] == nil {
		mal.aaByGroup[data.Group] = make([]*AltAdvanceData, 0)
	}
	mal.aaByGroup[data.Group] = append(mal.aaByGroup[data.Group], data)

	mal.totalLoaded++

	return nil
}

// GetAltAdvancement returns an AA by spell ID
func (mal *MasterAAList) GetAltAdvancement(spellID int32) *AltAdvanceData {
	mal.mutex.RLock()
	defer mal.mutex.RUnlock()

	if data, exists := mal.aaBySpellID[spellID]; exists {
		return data.Copy()
	}

	return nil
}

// GetAltAdvancementByNodeID returns an AA by node ID
func (mal *MasterAAList) GetAltAdvancementByNodeID(nodeID int32) *AltAdvanceData {
	mal.mutex.RLock()
	defer mal.mutex.RUnlock()

	if data, exists := mal.aaByNodeID[nodeID]; exists {
		return data.Copy()
	}

	return nil
}

// GetAAsByGroup returns all AAs for a specific group/tab
func (mal *MasterAAList) GetAAsByGroup(group int8) []*AltAdvanceData {
	mal.mutex.RLock()
	defer mal.mutex.RUnlock()

	if aaList, exists := mal.aaByGroup[group]; exists {
		// Return copies to prevent external modification
		result := make([]*AltAdvanceData, len(aaList))
		for i, aa := range aaList {
			result[i] = aa.Copy()
		}
		return result
	}

	return []*AltAdvanceData{}
}

// GetAAsByClass returns AAs available for a specific class
func (mal *MasterAAList) GetAAsByClass(classID int8) []*AltAdvanceData {
	mal.mutex.RLock()
	defer mal.mutex.RUnlock()

	var result []*AltAdvanceData

	for _, aa := range mal.aaList {
		// Check if AA is available for this class (0 means all classes)
		if aa.ClassReq == 0 || aa.ClassReq == classID {
			result = append(result, aa.Copy())
		}
	}

	return result
}

// GetAAsByLevel returns AAs available at a specific level
func (mal *MasterAAList) GetAAsByLevel(level int8) []*AltAdvanceData {
	mal.mutex.RLock()
	defer mal.mutex.RUnlock()

	var result []*AltAdvanceData

	for _, aa := range mal.aaList {
		if aa.MinLevel <= level {
			result = append(result, aa.Copy())
		}
	}

	return result
}

// Size returns the total number of AAs
func (mal *MasterAAList) Size() int {
	mal.mutex.RLock()
	defer mal.mutex.RUnlock()

	return len(mal.aaList)
}

// GetAllAAs returns all AAs (copies)
func (mal *MasterAAList) GetAllAAs() []*AltAdvanceData {
	mal.mutex.RLock()
	defer mal.mutex.RUnlock()

	result := make([]*AltAdvanceData, len(mal.aaList))
	for i, aa := range mal.aaList {
		result[i] = aa.Copy()
	}

	return result
}

// DestroyAltAdvancements clears all AA data
func (mal *MasterAAList) DestroyAltAdvancements() {
	mal.mutex.Lock()
	defer mal.mutex.Unlock()

	mal.aaList = make([]*AltAdvanceData, 0)
	mal.aaBySpellID = make(map[int32]*AltAdvanceData)
	mal.aaByNodeID = make(map[int32]*AltAdvanceData)
	mal.aaByGroup = make(map[int8][]*AltAdvanceData)
	mal.totalLoaded = 0
}

// SortAAsByGroup sorts AAs within each group by row and column
func (mal *MasterAAList) SortAAsByGroup() {
	mal.mutex.Lock()
	defer mal.mutex.Unlock()

	for group := range mal.aaByGroup {
		sort.Slice(mal.aaByGroup[group], func(i, j int) bool {
			aaI := mal.aaByGroup[group][i]
			aaJ := mal.aaByGroup[group][j]

			// Sort by row first, then by column
			if aaI.Row != aaJ.Row {
				return aaI.Row < aaJ.Row
			}
			return aaI.Col < aaJ.Col
		})
	}
}

// GetGroupCount returns the number of groups with AAs
func (mal *MasterAAList) GetGroupCount() int {
	mal.mutex.RLock()
	defer mal.mutex.RUnlock()

	return len(mal.aaByGroup)
}

// GetGroups returns all group IDs that have AAs
func (mal *MasterAAList) GetGroups() []int8 {
	mal.mutex.RLock()
	defer mal.mutex.RUnlock()

	groups := make([]int8, 0, len(mal.aaByGroup))
	for group := range mal.aaByGroup {
		groups = append(groups, group)
	}

	sort.Slice(groups, func(i, j int) bool {
		return groups[i] < groups[j]
	})

	return groups
}

// ValidateAAData validates all AA data for consistency
func (mal *MasterAAList) ValidateAAData() []error {
	mal.mutex.RLock()
	defer mal.mutex.RUnlock()

	var errors []error

	for _, aa := range mal.aaList {
		if !aa.IsValid() {
			errors = append(errors, fmt.Errorf("invalid AA data: spell_id=%d, node_id=%d", aa.SpellID, aa.NodeID))
		}

		// Validate prerequisites
		if aa.RankPrereqID > 0 {
			if _, exists := mal.aaByNodeID[aa.RankPrereqID]; !exists {
				errors = append(errors, fmt.Errorf("AA %d has invalid prerequisite node ID %d", aa.NodeID, aa.RankPrereqID))
			}
		}

		// Validate positioning
		if aa.Col < MIN_AA_COL || aa.Col > MAX_AA_COL {
			errors = append(errors, fmt.Errorf("AA %d has invalid column %d", aa.NodeID, aa.Col))
		}

		if aa.Row < MIN_AA_ROW || aa.Row > MAX_AA_ROW {
			errors = append(errors, fmt.Errorf("AA %d has invalid row %d", aa.NodeID, aa.Row))
		}

		// Validate costs and ranks
		if aa.RankCost < MIN_RANK_COST || aa.RankCost > MAX_RANK_COST {
			errors = append(errors, fmt.Errorf("AA %d has invalid rank cost %d", aa.NodeID, aa.RankCost))
		}

		if aa.MaxRank < MIN_MAX_RANK || aa.MaxRank > MAX_MAX_RANK {
			errors = append(errors, fmt.Errorf("AA %d has invalid max rank %d", aa.NodeID, aa.MaxRank))
		}
	}

	return errors
}

// GetStats returns statistics about the master AA list
func (mal *MasterAAList) GetStats() map[string]interface{} {
	mal.mutex.RLock()
	defer mal.mutex.RUnlock()

	stats := make(map[string]interface{})
	stats[STAT_TOTAL_AAS_LOADED] = mal.totalLoaded
	stats["last_load_time"] = mal.lastLoadTime
	stats["groups_count"] = len(mal.aaByGroup)

	// Count AAs per group
	groupCounts := make(map[int8]int)
	for group, aaList := range mal.aaByGroup {
		groupCounts[group] = len(aaList)
	}
	stats[STAT_AAS_PER_TAB] = groupCounts

	return stats
}

// NewMasterAANodeList creates a new master AA node list
func NewMasterAANodeList() *MasterAANodeList {
	return &MasterAANodeList{
		nodeList:     make([]*TreeNodeData, 0),
		nodesByClass: make(map[int32][]*TreeNodeData),
		nodesByTree:  make(map[int32]*TreeNodeData),
		totalLoaded:  0,
		lastLoadTime: time.Now(),
	}
}

// AddTreeNode adds a tree node to the master list
func (manl *MasterAANodeList) AddTreeNode(data *TreeNodeData) error {
	if data == nil {
		return fmt.Errorf("data cannot be nil")
	}

	manl.mutex.Lock()
	defer manl.mutex.Unlock()

	// Check for duplicates
	if _, exists := manl.nodesByTree[data.TreeID]; exists {
		return fmt.Errorf("tree node with tree ID %d already exists", data.TreeID)
	}

	// Add to main list
	manl.nodeList = append(manl.nodeList, data)

	// Add to lookup maps
	manl.nodesByTree[data.TreeID] = data

	// Add to class map
	if manl.nodesByClass[data.ClassID] == nil {
		manl.nodesByClass[data.ClassID] = make([]*TreeNodeData, 0)
	}
	manl.nodesByClass[data.ClassID] = append(manl.nodesByClass[data.ClassID], data)

	manl.totalLoaded++

	return nil
}

// GetTreeNodes returns all tree nodes
func (manl *MasterAANodeList) GetTreeNodes() []*TreeNodeData {
	manl.mutex.RLock()
	defer manl.mutex.RUnlock()

	// Return copies to prevent external modification
	result := make([]*TreeNodeData, len(manl.nodeList))
	for i, node := range manl.nodeList {
		nodeCopy := *node
		result[i] = &nodeCopy
	}

	return result
}

// GetTreeNodesByClass returns tree nodes for a specific class
func (manl *MasterAANodeList) GetTreeNodesByClass(classID int32) []*TreeNodeData {
	manl.mutex.RLock()
	defer manl.mutex.RUnlock()

	if nodeList, exists := manl.nodesByClass[classID]; exists {
		// Return copies to prevent external modification
		result := make([]*TreeNodeData, len(nodeList))
		for i, node := range nodeList {
			nodeCopy := *node
			result[i] = &nodeCopy
		}
		return result
	}

	return []*TreeNodeData{}
}

// GetTreeNode returns a specific tree node by tree ID
func (manl *MasterAANodeList) GetTreeNode(treeID int32) *TreeNodeData {
	manl.mutex.RLock()
	defer manl.mutex.RUnlock()

	if node, exists := manl.nodesByTree[treeID]; exists {
		nodeCopy := *node
		return &nodeCopy
	}

	return nil
}

// Size returns the total number of tree nodes
func (manl *MasterAANodeList) Size() int {
	manl.mutex.RLock()
	defer manl.mutex.RUnlock()

	return len(manl.nodeList)
}

// DestroyTreeNodes clears all tree node data
func (manl *MasterAANodeList) DestroyTreeNodes() {
	manl.mutex.Lock()
	defer manl.mutex.Unlock()

	manl.nodeList = make([]*TreeNodeData, 0)
	manl.nodesByClass = make(map[int32][]*TreeNodeData)
	manl.nodesByTree = make(map[int32]*TreeNodeData)
	manl.totalLoaded = 0
}

// GetClassCount returns the number of classes with tree nodes
func (manl *MasterAANodeList) GetClassCount() int {
	manl.mutex.RLock()
	defer manl.mutex.RUnlock()

	return len(manl.nodesByClass)
}

// GetClasses returns all class IDs that have tree nodes
func (manl *MasterAANodeList) GetClasses() []int32 {
	manl.mutex.RLock()
	defer manl.mutex.RUnlock()

	classes := make([]int32, 0, len(manl.nodesByClass))
	for classID := range manl.nodesByClass {
		classes = append(classes, classID)
	}

	sort.Slice(classes, func(i, j int) bool {
		return classes[i] < classes[j]
	})

	return classes
}

// ValidateTreeNodes validates all tree node data for consistency
func (manl *MasterAANodeList) ValidateTreeNodes() []error {
	manl.mutex.RLock()
	defer manl.mutex.RUnlock()

	var errors []error

	// Check for orphaned tree IDs
	treeIDMap := make(map[int32]bool)
	for _, node := range manl.nodeList {
		treeIDMap[node.TreeID] = true
	}

	// Check for duplicate class/tree combinations
	classTreeMap := make(map[string]bool)
	for _, node := range manl.nodeList {
		key := fmt.Sprintf("%d_%d", node.ClassID, node.TreeID)
		if classTreeMap[key] {
			errors = append(errors, fmt.Errorf("duplicate class/tree combination: class=%d, tree=%d", node.ClassID, node.TreeID))
		}
		classTreeMap[key] = true
	}

	return errors
}

// GetStats returns statistics about the master node list
func (manl *MasterAANodeList) GetStats() map[string]interface{} {
	manl.mutex.RLock()
	defer manl.mutex.RUnlock()

	stats := make(map[string]interface{})
	stats[STAT_TOTAL_NODES_LOADED] = manl.totalLoaded
	stats["last_load_time"] = manl.lastLoadTime
	stats["classes_count"] = len(manl.nodesByClass)

	// Count nodes per class
	classCounts := make(map[int32]int)
	for classID, nodeList := range manl.nodesByClass {
		classCounts[classID] = len(nodeList)
	}
	stats["nodes_per_class"] = classCounts

	return stats
}

// BuildAATreeMap builds a map of AA tree configurations for a specific class
func (manl *MasterAANodeList) BuildAATreeMap(classID int32) map[int8]int32 {
	nodes := manl.GetTreeNodesByClass(classID)
	treeMap := make(map[int8]int32)

	// Map each tab/group to its corresponding tree ID
	for i, node := range nodes {
		if i < 10 { // Limit to the number of defined AA tabs
			treeMap[int8(i)] = node.TreeID
		}
	}

	return treeMap
}

// GetTreeIDForTab returns the tree ID for a specific tab and class
func (manl *MasterAANodeList) GetTreeIDForTab(classID int32, tab int8) int32 {
	nodes := manl.GetTreeNodesByClass(classID)

	if int(tab) < len(nodes) {
		return nodes[tab].TreeID
	}

	return 0
}
