package recipes

import (
	"strings"
	"sync"
)

// MasterRecipeList manages all recipes in the system
// Converted from C++ MasterRecipeList class
type MasterRecipeList struct {
	recipes    map[int32]*Recipe    // Recipe ID -> Recipe
	recipesCRC map[int32]*Recipe    // SOE CRC ID -> Recipe
	nameIndex  map[string]*Recipe   // Lowercase name -> Recipe
	bookIndex  map[string][]*Recipe // Lowercase book name -> Recipes
	skillIndex map[int32][]*Recipe  // Skill ID -> Recipes
	tierIndex  map[int8][]*Recipe   // Tier -> Recipes
	mutex      sync.RWMutex
	stats      *Statistics
}

// NewMasterRecipeList creates a new master recipe list
// Converted from C++ MasterRecipeList::MasterRecipeList constructor
func NewMasterRecipeList() *MasterRecipeList {
	return &MasterRecipeList{
		recipes:    make(map[int32]*Recipe),
		recipesCRC: make(map[int32]*Recipe),
		nameIndex:  make(map[string]*Recipe),
		bookIndex:  make(map[string][]*Recipe),
		skillIndex: make(map[int32][]*Recipe),
		tierIndex:  make(map[int8][]*Recipe),
		stats:      NewStatistics(),
	}
}

// AddRecipe adds a recipe to the master list
// Converted from C++ MasterRecipeList::AddRecipe
func (mrl *MasterRecipeList) AddRecipe(recipe *Recipe) bool {
	if recipe == nil || !recipe.IsValid() {
		return false
	}

	mrl.mutex.Lock()
	defer mrl.mutex.Unlock()

	// Check for duplicate ID
	if _, exists := mrl.recipes[recipe.ID]; exists {
		return false
	}

	// Add to main map
	mrl.recipes[recipe.ID] = recipe

	// Add to CRC map if SOE ID is set
	if recipe.SoeID != 0 {
		mrl.recipesCRC[recipe.SoeID] = recipe
	}

	// Add to name index
	nameLower := strings.ToLower(strings.TrimSpace(recipe.Name))
	if nameLower != "" {
		mrl.nameIndex[nameLower] = recipe
	}

	// Add to book index
	bookLower := strings.ToLower(strings.TrimSpace(recipe.Book))
	if bookLower != "" {
		mrl.bookIndex[bookLower] = append(mrl.bookIndex[bookLower], recipe)
	}

	// Add to skill index
	if recipe.Skill != 0 {
		mrl.skillIndex[recipe.Skill] = append(mrl.skillIndex[recipe.Skill], recipe)
	}

	// Add to tier index
	if recipe.Tier > 0 {
		mrl.tierIndex[recipe.Tier] = append(mrl.tierIndex[recipe.Tier], recipe)
	}

	// Update statistics
	mrl.stats.TotalRecipes++
	mrl.stats.RecipesByTier[recipe.Tier]++
	mrl.stats.RecipesBySkill[recipe.Skill]++

	return true
}

// GetRecipe retrieves a recipe by ID
// Converted from C++ MasterRecipeList::GetRecipe
func (mrl *MasterRecipeList) GetRecipe(recipeID int32) *Recipe {
	mrl.mutex.RLock()
	defer mrl.mutex.RUnlock()

	mrl.stats.IncrementRecipeLookups()

	if recipe, exists := mrl.recipes[recipeID]; exists {
		return recipe
	}

	return nil
}

// GetRecipeByCRC retrieves a recipe by SOE CRC ID
// Converted from C++ MasterRecipeList::GetRecipeByCRC
func (mrl *MasterRecipeList) GetRecipeByCRC(recipeCRC int32) *Recipe {
	mrl.mutex.RLock()
	defer mrl.mutex.RUnlock()

	mrl.stats.IncrementRecipeLookups()

	if recipe, exists := mrl.recipesCRC[recipeCRC]; exists {
		return recipe
	}

	return nil
}

// GetRecipeByName retrieves a recipe by name (case-insensitive)
// Converted from C++ MasterRecipeList::GetRecipeByName
func (mrl *MasterRecipeList) GetRecipeByName(name string) *Recipe {
	mrl.mutex.RLock()
	defer mrl.mutex.RUnlock()

	mrl.stats.IncrementRecipeLookups()

	nameLower := strings.ToLower(strings.TrimSpace(name))
	if recipe, exists := mrl.nameIndex[nameLower]; exists {
		return recipe
	}

	return nil
}

// GetRecipesByBook retrieves all recipes for a given book name
// Converted from C++ MasterRecipeList::GetRecipes
func (mrl *MasterRecipeList) GetRecipesByBook(bookName string) []*Recipe {
	mrl.mutex.RLock()
	defer mrl.mutex.RUnlock()

	mrl.stats.IncrementRecipeLookups()

	bookLower := strings.ToLower(strings.TrimSpace(bookName))
	if recipes, exists := mrl.bookIndex[bookLower]; exists {
		// Return a copy to prevent external modification
		result := make([]*Recipe, len(recipes))
		copy(result, recipes)
		return result
	}

	return nil
}

// GetRecipesBySkill retrieves all recipes for a given skill
func (mrl *MasterRecipeList) GetRecipesBySkill(skillID int32) []*Recipe {
	mrl.mutex.RLock()
	defer mrl.mutex.RUnlock()

	mrl.stats.IncrementRecipeLookups()

	if recipes, exists := mrl.skillIndex[skillID]; exists {
		// Return a copy to prevent external modification
		result := make([]*Recipe, len(recipes))
		copy(result, recipes)
		return result
	}

	return nil
}

// GetRecipesByTier retrieves all recipes for a given tier
func (mrl *MasterRecipeList) GetRecipesByTier(tier int8) []*Recipe {
	mrl.mutex.RLock()
	defer mrl.mutex.RUnlock()

	mrl.stats.IncrementRecipeLookups()

	if recipes, exists := mrl.tierIndex[tier]; exists {
		// Return a copy to prevent external modification
		result := make([]*Recipe, len(recipes))
		copy(result, recipes)
		return result
	}

	return nil
}

// GetRecipesByClass retrieves all recipes that can be used by a tradeskill class
func (mrl *MasterRecipeList) GetRecipesByClass(classID int8) []*Recipe {
	mrl.mutex.RLock()
	defer mrl.mutex.RUnlock()

	mrl.stats.IncrementRecipeLookups()

	var result []*Recipe
	for _, recipe := range mrl.recipes {
		if recipe.CanUseRecipeByClass(classID) {
			result = append(result, recipe)
		}
	}

	return result
}

// GetRecipesByLevel retrieves all recipes within a level range
func (mrl *MasterRecipeList) GetRecipesByLevel(minLevel, maxLevel int8) []*Recipe {
	mrl.mutex.RLock()
	defer mrl.mutex.RUnlock()

	mrl.stats.IncrementRecipeLookups()

	var result []*Recipe
	for _, recipe := range mrl.recipes {
		if recipe.Level >= minLevel && recipe.Level <= maxLevel {
			result = append(result, recipe)
		}
	}

	return result
}

// RemoveRecipe removes a recipe from the master list
func (mrl *MasterRecipeList) RemoveRecipe(recipeID int32) bool {
	mrl.mutex.Lock()
	defer mrl.mutex.Unlock()

	recipe, exists := mrl.recipes[recipeID]
	if !exists {
		return false
	}

	// Remove from main map
	delete(mrl.recipes, recipeID)

	// Remove from CRC map
	if recipe.SoeID != 0 {
		delete(mrl.recipesCRC, recipe.SoeID)
	}

	// Remove from name index
	nameLower := strings.ToLower(strings.TrimSpace(recipe.Name))
	if nameLower != "" {
		delete(mrl.nameIndex, nameLower)
	}

	// Remove from book index
	bookLower := strings.ToLower(strings.TrimSpace(recipe.Book))
	if bookLower != "" {
		if recipes, exists := mrl.bookIndex[bookLower]; exists {
			for i, r := range recipes {
				if r.ID == recipeID {
					mrl.bookIndex[bookLower] = append(recipes[:i], recipes[i+1:]...)
					break
				}
			}
			if len(mrl.bookIndex[bookLower]) == 0 {
				delete(mrl.bookIndex, bookLower)
			}
		}
	}

	// Remove from skill index
	if recipe.Skill != 0 {
		if recipes, exists := mrl.skillIndex[recipe.Skill]; exists {
			for i, r := range recipes {
				if r.ID == recipeID {
					mrl.skillIndex[recipe.Skill] = append(recipes[:i], recipes[i+1:]...)
					break
				}
			}
			if len(mrl.skillIndex[recipe.Skill]) == 0 {
				delete(mrl.skillIndex, recipe.Skill)
			}
		}
	}

	// Remove from tier index
	if recipe.Tier > 0 {
		if recipes, exists := mrl.tierIndex[recipe.Tier]; exists {
			for i, r := range recipes {
				if r.ID == recipeID {
					mrl.tierIndex[recipe.Tier] = append(recipes[:i], recipes[i+1:]...)
					break
				}
			}
			if len(mrl.tierIndex[recipe.Tier]) == 0 {
				delete(mrl.tierIndex, recipe.Tier)
			}
		}
	}

	// Update statistics
	mrl.stats.TotalRecipes--
	mrl.stats.RecipesByTier[recipe.Tier]--
	mrl.stats.RecipesBySkill[recipe.Skill]--

	return true
}

// ClearRecipes removes all recipes from the master list
// Converted from C++ MasterRecipeList::ClearRecipes
func (mrl *MasterRecipeList) ClearRecipes() {
	mrl.mutex.Lock()
	defer mrl.mutex.Unlock()

	mrl.recipes = make(map[int32]*Recipe)
	mrl.recipesCRC = make(map[int32]*Recipe)
	mrl.nameIndex = make(map[string]*Recipe)
	mrl.bookIndex = make(map[string][]*Recipe)
	mrl.skillIndex = make(map[int32][]*Recipe)
	mrl.tierIndex = make(map[int8][]*Recipe)

	// Reset statistics
	mrl.stats.TotalRecipes = 0
	mrl.stats.RecipesByTier = make(map[int8]int32)
	mrl.stats.RecipesBySkill = make(map[int32]int32)
}

// Size returns the total number of recipes
// Converted from C++ MasterRecipeList::Size
func (mrl *MasterRecipeList) Size() int32 {
	mrl.mutex.RLock()
	defer mrl.mutex.RUnlock()

	return int32(len(mrl.recipes))
}

// GetAllRecipes returns all recipes (use with caution for large lists)
func (mrl *MasterRecipeList) GetAllRecipes() map[int32]*Recipe {
	mrl.mutex.RLock()
	defer mrl.mutex.RUnlock()

	// Return a copy to prevent external modification
	result := make(map[int32]*Recipe)
	for id, recipe := range mrl.recipes {
		result[id] = recipe
	}

	return result
}

// GetRecipeIDs returns all recipe IDs
func (mrl *MasterRecipeList) GetRecipeIDs() []int32 {
	mrl.mutex.RLock()
	defer mrl.mutex.RUnlock()

	result := make([]int32, 0, len(mrl.recipes))
	for id := range mrl.recipes {
		result = append(result, id)
	}

	return result
}

// GetStatistics returns a snapshot of the current statistics
func (mrl *MasterRecipeList) GetStatistics() Statistics {
	return mrl.stats.GetSnapshot()
}

// GetSkills returns all skills that have recipes
func (mrl *MasterRecipeList) GetSkills() []int32 {
	mrl.mutex.RLock()
	defer mrl.mutex.RUnlock()

	result := make([]int32, 0, len(mrl.skillIndex))
	for skill := range mrl.skillIndex {
		result = append(result, skill)
	}

	return result
}

// GetTiers returns all tiers that have recipes
func (mrl *MasterRecipeList) GetTiers() []int8 {
	mrl.mutex.RLock()
	defer mrl.mutex.RUnlock()

	result := make([]int8, 0, len(mrl.tierIndex))
	for tier := range mrl.tierIndex {
		result = append(result, tier)
	}

	return result
}

// GetBookNames returns all book names that have recipes
func (mrl *MasterRecipeList) GetBookNames() []string {
	mrl.mutex.RLock()
	defer mrl.mutex.RUnlock()

	result := make([]string, 0, len(mrl.bookIndex))
	for book := range mrl.bookIndex {
		result = append(result, book)
	}

	return result
}
