package heroic_ops

import (
	"context"
	"fmt"
	"sort"
)

// NewMasterHeroicOPList creates a new master heroic opportunity list
func NewMasterHeroicOPList() *MasterHeroicOPList {
	return &MasterHeroicOPList{
		starters: make(map[int8]map[int32]*HeroicOPStarter),
		wheels:   make(map[int32][]*HeroicOPWheel),
		spells:   make(map[int32]SpellInfo),
		loaded:   false,
	}
}

// LoadFromDatabase loads all heroic opportunities from the database
func (mhol *MasterHeroicOPList) LoadFromDatabase(ctx context.Context, database HeroicOPDatabase) error {
	mhol.mu.Lock()
	defer mhol.mu.Unlock()

	// Clear existing data
	mhol.starters = make(map[int8]map[int32]*HeroicOPStarter)
	mhol.wheels = make(map[int32][]*HeroicOPWheel)
	mhol.spells = make(map[int32]SpellInfo)

	// Load starters
	starterData, err := database.LoadStarters(ctx)
	if err != nil {
		return fmt.Errorf("failed to load starters: %w", err)
	}

	for _, data := range starterData {
		starter := &HeroicOPStarter{
			ID:          data.ID,
			StartClass:  data.StarterClass,
			StarterIcon: data.StarterIcon,
			Name:        data.Name,
			Description: data.Description,
			Abilities: [6]int16{
				data.Ability1, data.Ability2, data.Ability3,
				data.Ability4, data.Ability5, data.Ability6,
			},
			SaveNeeded: false,
		}

		// Validate starter
		if err := starter.Validate(); err != nil {
			continue // Skip invalid starters
		}

		// Add to map structure
		if mhol.starters[starter.StartClass] == nil {
			mhol.starters[starter.StartClass] = make(map[int32]*HeroicOPStarter)
		}
		mhol.starters[starter.StartClass][starter.ID] = starter
	}

	// Load wheels
	wheelData, err := database.LoadWheels(ctx)
	if err != nil {
		return fmt.Errorf("failed to load wheels: %w", err)
	}

	for _, data := range wheelData {
		wheel := &HeroicOPWheel{
			ID:            data.ID,
			StarterLinkID: data.StarterLinkID,
			Order:         data.ChainOrder,
			ShiftIcon:     data.ShiftIcon,
			Chance:        data.Chance,
			SpellID:       data.SpellID,
			Name:          data.Name,
			Description:   data.Description,
			Abilities: [6]int16{
				data.Ability1, data.Ability2, data.Ability3,
				data.Ability4, data.Ability5, data.Ability6,
			},
			SaveNeeded: false,
		}

		// Validate wheel
		if err := wheel.Validate(); err != nil {
			continue // Skip invalid wheels
		}

		// Add to wheels map
		mhol.wheels[wheel.StarterLinkID] = append(mhol.wheels[wheel.StarterLinkID], wheel)

		// Store spell info
		mhol.spells[wheel.SpellID] = SpellInfo{
			ID:          wheel.SpellID,
			Name:        wheel.Name,
			Description: wheel.Description,
		}
	}

	mhol.loaded = true
	return nil
}

// GetStartersForClass returns all starters that the specified class can initiate
func (mhol *MasterHeroicOPList) GetStartersForClass(playerClass int8) []*HeroicOPStarter {
	mhol.mu.RLock()
	defer mhol.mu.RUnlock()

	var starters []*HeroicOPStarter

	// Add class-specific starters
	if classStarters, exists := mhol.starters[playerClass]; exists {
		for _, starter := range classStarters {
			starters = append(starters, starter)
		}
	}

	// Add universal starters (class 0 = any)
	if universalStarters, exists := mhol.starters[ClassAny]; exists {
		for _, starter := range universalStarters {
			starters = append(starters, starter)
		}
	}

	return starters
}

// GetStarter returns a specific starter by ID
func (mhol *MasterHeroicOPList) GetStarter(starterID int32) *HeroicOPStarter {
	mhol.mu.RLock()
	defer mhol.mu.RUnlock()

	// Search through all classes
	for _, classStarters := range mhol.starters {
		if starter, exists := classStarters[starterID]; exists {
			return starter
		}
	}

	return nil
}

// GetWheelsForStarter returns all wheels associated with a starter
func (mhol *MasterHeroicOPList) GetWheelsForStarter(starterID int32) []*HeroicOPWheel {
	mhol.mu.RLock()
	defer mhol.mu.RUnlock()

	if wheels, exists := mhol.wheels[starterID]; exists {
		// Return a copy to prevent external modification
		result := make([]*HeroicOPWheel, len(wheels))
		copy(result, wheels)
		return result
	}

	return nil
}

// GetWheel returns a specific wheel by ID
func (mhol *MasterHeroicOPList) GetWheel(wheelID int32) *HeroicOPWheel {
	mhol.mu.RLock()
	defer mhol.mu.RUnlock()

	// Search through all wheel lists
	for _, wheelList := range mhol.wheels {
		for _, wheel := range wheelList {
			if wheel.ID == wheelID {
				return wheel
			}
		}
	}

	return nil
}

// SelectRandomWheel randomly selects a wheel from the starter's available wheels
func (mhol *MasterHeroicOPList) SelectRandomWheel(starterID int32) *HeroicOPWheel {
	wheels := mhol.GetWheelsForStarter(starterID)
	if len(wheels) == 0 {
		return nil
	}

	return SelectRandomWheel(wheels)
}

// GetSpellInfo returns spell information for a given spell ID
func (mhol *MasterHeroicOPList) GetSpellInfo(spellID int32) (*SpellInfo, bool) {
	mhol.mu.RLock()
	defer mhol.mu.RUnlock()

	if spell, exists := mhol.spells[spellID]; exists {
		return &spell, true
	}

	return nil, false
}

// AddStarter adds a new starter to the master list
func (mhol *MasterHeroicOPList) AddStarter(starter *HeroicOPStarter) error {
	mhol.mu.Lock()
	defer mhol.mu.Unlock()

	if err := starter.Validate(); err != nil {
		return fmt.Errorf("invalid starter: %w", err)
	}

	// Check for duplicate ID
	if existingStarter := mhol.getStarterNoLock(starter.ID); existingStarter != nil {
		return fmt.Errorf("starter ID %d already exists", starter.ID)
	}

	// Add to map structure
	if mhol.starters[starter.StartClass] == nil {
		mhol.starters[starter.StartClass] = make(map[int32]*HeroicOPStarter)
	}
	mhol.starters[starter.StartClass][starter.ID] = starter

	return nil
}

// AddWheel adds a new wheel to the master list
func (mhol *MasterHeroicOPList) AddWheel(wheel *HeroicOPWheel) error {
	mhol.mu.Lock()
	defer mhol.mu.Unlock()

	if err := wheel.Validate(); err != nil {
		return fmt.Errorf("invalid wheel: %w", err)
	}

	// Check for duplicate ID
	if existingWheel := mhol.getWheelNoLock(wheel.ID); existingWheel != nil {
		return fmt.Errorf("wheel ID %d already exists", wheel.ID)
	}

	// Verify starter exists
	if mhol.getStarterNoLock(wheel.StarterLinkID) == nil {
		return fmt.Errorf("starter ID %d not found for wheel", wheel.StarterLinkID)
	}

	// Add to wheels map
	mhol.wheels[wheel.StarterLinkID] = append(mhol.wheels[wheel.StarterLinkID], wheel)

	// Store spell info
	mhol.spells[wheel.SpellID] = SpellInfo{
		ID:          wheel.SpellID,
		Name:        wheel.Name,
		Description: wheel.Description,
	}

	return nil
}

// RemoveStarter removes a starter and all its associated wheels
func (mhol *MasterHeroicOPList) RemoveStarter(starterID int32) bool {
	mhol.mu.Lock()
	defer mhol.mu.Unlock()

	// Find and remove starter
	found := false
	for class, classStarters := range mhol.starters {
		if _, exists := classStarters[starterID]; exists {
			delete(classStarters, starterID)
			found = true

			// Clean up empty class map
			if len(classStarters) == 0 {
				delete(mhol.starters, class)
			}

			break
		}
	}

	if !found {
		return false
	}

	// Remove associated wheels
	delete(mhol.wheels, starterID)

	return true
}

// RemoveWheel removes a specific wheel
func (mhol *MasterHeroicOPList) RemoveWheel(wheelID int32) bool {
	mhol.mu.Lock()
	defer mhol.mu.Unlock()

	// Find and remove wheel
	for starterID, wheelList := range mhol.wheels {
		for i, wheel := range wheelList {
			if wheel.ID == wheelID {
				// Remove wheel from slice
				mhol.wheels[starterID] = append(wheelList[:i], wheelList[i+1:]...)

				// Clean up empty wheel list
				if len(mhol.wheels[starterID]) == 0 {
					delete(mhol.wheels, starterID)
				}

				return true
			}
		}
	}

	return false
}

// GetAllStarters returns all starters in the system
func (mhol *MasterHeroicOPList) GetAllStarters() []*HeroicOPStarter {
	mhol.mu.RLock()
	defer mhol.mu.RUnlock()

	var allStarters []*HeroicOPStarter

	for _, classStarters := range mhol.starters {
		for _, starter := range classStarters {
			allStarters = append(allStarters, starter)
		}
	}

	// Sort by ID for consistent ordering
	sort.Slice(allStarters, func(i, j int) bool {
		return allStarters[i].ID < allStarters[j].ID
	})

	return allStarters
}

// GetAllWheels returns all wheels in the system
func (mhol *MasterHeroicOPList) GetAllWheels() []*HeroicOPWheel {
	mhol.mu.RLock()
	defer mhol.mu.RUnlock()

	var allWheels []*HeroicOPWheel

	for _, wheelList := range mhol.wheels {
		allWheels = append(allWheels, wheelList...)
	}

	// Sort by ID for consistent ordering
	sort.Slice(allWheels, func(i, j int) bool {
		return allWheels[i].ID < allWheels[j].ID
	})

	return allWheels
}

// GetStarterCount returns the total number of starters
func (mhol *MasterHeroicOPList) GetStarterCount() int {
	mhol.mu.RLock()
	defer mhol.mu.RUnlock()

	count := 0
	for _, classStarters := range mhol.starters {
		count += len(classStarters)
	}

	return count
}

// GetWheelCount returns the total number of wheels
func (mhol *MasterHeroicOPList) GetWheelCount() int {
	mhol.mu.RLock()
	defer mhol.mu.RUnlock()

	count := 0
	for _, wheelList := range mhol.wheels {
		count += len(wheelList)
	}

	return count
}

// IsLoaded returns whether data has been loaded
func (mhol *MasterHeroicOPList) IsLoaded() bool {
	mhol.mu.RLock()
	defer mhol.mu.RUnlock()

	return mhol.loaded
}

// SearchStarters searches for starters matching the given criteria
func (mhol *MasterHeroicOPList) SearchStarters(criteria HeroicOPSearchCriteria) []*HeroicOPStarter {
	mhol.mu.RLock()
	defer mhol.mu.RUnlock()

	var results []*HeroicOPStarter

	for _, classStarters := range mhol.starters {
		for _, starter := range classStarters {
			if mhol.matchesStarterCriteria(starter, criteria) {
				results = append(results, starter)
			}
		}
	}

	// Sort results by ID
	sort.Slice(results, func(i, j int) bool {
		return results[i].ID < results[j].ID
	})

	return results
}

// SearchWheels searches for wheels matching the given criteria
func (mhol *MasterHeroicOPList) SearchWheels(criteria HeroicOPSearchCriteria) []*HeroicOPWheel {
	mhol.mu.RLock()
	defer mhol.mu.RUnlock()

	var results []*HeroicOPWheel

	for _, wheelList := range mhol.wheels {
		for _, wheel := range wheelList {
			if mhol.matchesWheelCriteria(wheel, criteria) {
				results = append(results, wheel)
			}
		}
	}

	// Sort results by ID
	sort.Slice(results, func(i, j int) bool {
		return results[i].ID < results[j].ID
	})

	return results
}

// GetStatistics returns usage statistics for the HO system
func (mhol *MasterHeroicOPList) GetStatistics() map[string]interface{} {
	mhol.mu.RLock()
	defer mhol.mu.RUnlock()

	stats := make(map[string]interface{})

	// Basic counts
	stats["total_starters"] = mhol.getStarterCountNoLock()
	stats["total_wheels"] = mhol.getWheelCountNoLock()
	stats["total_spells"] = len(mhol.spells)
	stats["loaded"] = mhol.loaded

	// Class distribution
	classDistribution := make(map[string]int)
	for class, classStarters := range mhol.starters {
		if className, exists := ClassNames[class]; exists {
			classDistribution[className] = len(classStarters)
		} else {
			classDistribution[fmt.Sprintf("Class_%d", class)] = len(classStarters)
		}
	}
	stats["class_distribution"] = classDistribution

	// Wheel distribution per starter
	wheelDistribution := make(map[string]int)
	for starterID, wheelList := range mhol.wheels {
		wheelDistribution[fmt.Sprintf("starter_%d", starterID)] = len(wheelList)
	}
	stats["wheel_distribution"] = wheelDistribution

	return stats
}

// Validate checks the integrity of the master list
func (mhol *MasterHeroicOPList) Validate() []error {
	mhol.mu.RLock()
	defer mhol.mu.RUnlock()

	var errors []error

	// Validate all starters
	for _, classStarters := range mhol.starters {
		for _, starter := range classStarters {
			if err := starter.Validate(); err != nil {
				errors = append(errors, fmt.Errorf("starter %d: %w", starter.ID, err))
			}
		}
	}

	// Validate all wheels
	for _, wheelList := range mhol.wheels {
		for _, wheel := range wheelList {
			if err := wheel.Validate(); err != nil {
				errors = append(errors, fmt.Errorf("wheel %d: %w", wheel.ID, err))
			}

			// Check if starter exists for this wheel
			if mhol.getStarterNoLock(wheel.StarterLinkID) == nil {
				errors = append(errors, fmt.Errorf("wheel %d references non-existent starter %d", wheel.ID, wheel.StarterLinkID))
			}
		}
	}

	// Check for orphaned wheels (starters with no wheels)
	for _, classStarters := range mhol.starters {
		for starterID := range classStarters {
			if _, hasWheels := mhol.wheels[starterID]; !hasWheels {
				errors = append(errors, fmt.Errorf("starter %d has no associated wheels", starterID))
			}
		}
	}

	return errors
}

// Internal helper methods (no lock versions)

func (mhol *MasterHeroicOPList) getStarterNoLock(starterID int32) *HeroicOPStarter {
	for _, classStarters := range mhol.starters {
		if starter, exists := classStarters[starterID]; exists {
			return starter
		}
	}
	return nil
}

func (mhol *MasterHeroicOPList) getWheelNoLock(wheelID int32) *HeroicOPWheel {
	for _, wheelList := range mhol.wheels {
		for _, wheel := range wheelList {
			if wheel.ID == wheelID {
				return wheel
			}
		}
	}
	return nil
}

func (mhol *MasterHeroicOPList) getStarterCountNoLock() int {
	count := 0
	for _, classStarters := range mhol.starters {
		count += len(classStarters)
	}
	return count
}

func (mhol *MasterHeroicOPList) getWheelCountNoLock() int {
	count := 0
	for _, wheelList := range mhol.wheels {
		count += len(wheelList)
	}
	return count
}

func (mhol *MasterHeroicOPList) matchesStarterCriteria(starter *HeroicOPStarter, criteria HeroicOPSearchCriteria) bool {
	// Class filter
	if criteria.StarterClass != 0 && starter.StartClass != criteria.StarterClass {
		return false
	}

	// Name pattern filter
	if criteria.NamePattern != "" {
		// Simple case-insensitive substring match
		// In a real implementation, you might want to use regular expressions
		if !containsIgnoreCase(starter.Name, criteria.NamePattern) {
			return false
		}
	}

	return true
}

func (mhol *MasterHeroicOPList) matchesWheelCriteria(wheel *HeroicOPWheel, criteria HeroicOPSearchCriteria) bool {
	// Spell ID filter
	if criteria.SpellID != 0 && wheel.SpellID != criteria.SpellID {
		return false
	}

	// Chance range filter
	if criteria.MinChance > 0 && wheel.Chance < criteria.MinChance {
		return false
	}

	if criteria.MaxChance > 0 && wheel.Chance > criteria.MaxChance {
		return false
	}

	// Required players filter
	if criteria.RequiredPlayers > 0 && wheel.RequiredPlayers != criteria.RequiredPlayers {
		return false
	}

	// Name pattern filter
	if criteria.NamePattern != "" {
		if !containsIgnoreCase(wheel.Name, criteria.NamePattern) {
			return false
		}
	}

	// Shift availability filter
	if criteria.HasShift && !wheel.HasShift() {
		return false
	}

	// Order type filter
	if criteria.IsOrdered && !wheel.IsOrdered() {
		return false
	}

	return true
}

// Simple case-insensitive substring search
func containsIgnoreCase(s, substr string) bool {
	// Convert both strings to lowercase for comparison
	// In a real implementation, you might want to use strings.ToLower
	// or a proper Unicode-aware comparison
	return len(substr) == 0 // Empty substring matches everything
	// TODO: Implement proper case-insensitive search
}
