package recipes

import (
	"fmt"
	"sync"

	"eq2emu/internal/database"
)

// RecipeManager provides high-level recipe system management with database integration
type RecipeManager struct {
	db                   *database.DB
	masterRecipeList     *MasterRecipeList
	masterRecipeBookList *MasterRecipeBookList
	loadedRecipes        map[int32]*Recipe
	loadedRecipeBooks    map[int32]*Recipe
	mu                   sync.RWMutex
	statisticsEnabled    bool

	// Statistics
	stats RecipeManagerStats
}

// RecipeManagerStats tracks recipe system usage and performance metrics
type RecipeManagerStats struct {
	TotalRecipesLoaded     int32
	TotalRecipeBooksLoaded int32
	PlayersWithRecipes     int32
	LoadOperations         int32
	SaveOperations         int32
	mu                     sync.RWMutex
}

// NewRecipeManager creates a new recipe manager with database integration
func NewRecipeManager(db *database.DB) *RecipeManager {
	return &RecipeManager{
		db:                   db,
		masterRecipeList:     NewMasterRecipeList(),
		masterRecipeBookList: NewMasterRecipeBookList(),
		loadedRecipes:        make(map[int32]*Recipe),
		loadedRecipeBooks:    make(map[int32]*Recipe),
		statisticsEnabled:    true,
	}
}

// LoadRecipes loads all recipes from the database with complex component relationships
func (rm *RecipeManager) LoadRecipes() error {
	rm.mu.Lock()
	defer rm.mu.Unlock()

	query := `SELECT r.id, r.soe_id, r.level, r.icon, r.skill_level, r.technique, r.knowledge,
		r.name, r.description, i.name as book, r.bench, ipc.adventure_classes,
		r.stage4_id, r.name, r.stage4_qty, pcl.name as primary_comp_title, r.primary_comp_qty,
		fcl.name as fuel_comp_title, r.fuel_comp_qty,
		bc.name AS build_comp_title, bc.qty AS build_comp_qty,
		bc2.name AS build2_comp_title, bc2.qty AS build2_comp_qty,
		bc3.name AS build3_comp_title, bc3.qty AS build3_comp_qty,
		bc4.name AS build4_comp_title, bc4.qty AS build4_comp_qty,
		r.stage0_id, r.stage1_id, r.stage2_id, r.stage3_id, r.stage4_id,
		r.stage0_qty, r.stage1_qty, r.stage2_qty, r.stage3_qty, r.stage4_qty,
		r.stage0_byp_id, r.stage1_byp_id, r.stage2_byp_id, r.stage3_byp_id, r.stage4_byp_id,
		r.stage0_byp_qty, r.stage1_byp_qty, r.stage2_byp_qty, r.stage3_byp_qty, r.stage4_byp_qty
		FROM recipe r
		LEFT JOIN ((SELECT recipe_id, soe_recipe_crc FROM item_details_recipe_items GROUP BY soe_recipe_crc) as idri) ON idri.soe_recipe_crc = r.soe_id
		LEFT JOIN items i ON idri.recipe_id = i.id
		INNER JOIN items ipc ON r.stage4_id = ipc.id
		INNER JOIN recipe_comp_list pcl ON r.primary_comp_list = pcl.id
		INNER JOIN recipe_comp_list fcl ON r.fuel_comp_list = fcl.id
		LEFT JOIN (SELECT rsc.recipe_id, rsc.comp_list, rsc.index, rcl.name, rsc.qty FROM recipe_secondary_comp rsc INNER JOIN recipe_comp_list rcl ON rcl.id = rsc.comp_list WHERE index = 0) AS bc ON bc.recipe_id = r.id
		LEFT JOIN (SELECT rsc.recipe_id, rsc.comp_list, rsc.index, rcl.name, rsc.qty FROM recipe_secondary_comp rsc INNER JOIN recipe_comp_list rcl ON rcl.id = rsc.comp_list WHERE index = 1) AS bc2 ON bc2.recipe_id = r.id
		LEFT JOIN (SELECT rsc.recipe_id, rsc.comp_list, rsc.index, rcl.name, rsc.qty FROM recipe_secondary_comp rsc INNER JOIN recipe_comp_list rcl ON rcl.id = rsc.comp_list WHERE index = 2) AS bc3 ON bc3.recipe_id = r.id
		LEFT JOIN (SELECT rsc.recipe_id, rsc.comp_list, rsc.index, rcl.name, rsc.qty FROM recipe_secondary_comp rsc INNER JOIN recipe_comp_list rcl ON rcl.id = rsc.comp_list WHERE index = 3) AS bc4 ON bc4.recipe_id = r.id
		WHERE r.bHaveAllProducts`

	loadedCount := int32(0)

	err := rm.db.Query(query, func(row *database.Row) error {
		recipe := NewRecipe()

		// Column index for scanning
		col := 0

		recipe.ID = int32(row.Int(col))
		col++
		recipe.SoeID = int32(row.Int(col))
		col++
		recipe.Level = int8(row.Int(col))
		col++
		recipe.Icon = int16(row.Int(col))
		col++
		recipe.Skill = int32(row.Int(col))
		col++
		recipe.Technique = int32(row.Int(col))
		col++
		recipe.Knowledge = int32(row.Int(col))
		col++
		recipe.Name = row.Text(col)
		col++
		recipe.Description = row.Text(col)
		col++

		// Book name (nullable)
		if !row.IsNull(col) {
			recipe.Book = row.Text(col)
		}
		col++

		// Device (nullable)
		if !row.IsNull(col) {
			recipe.Device = row.Text(col)
		}
		col++

		recipe.Classes = int32(row.Int(col))
		col++

		// Product information
		recipe.ProductItemID = int32(row.Int(col))
		col++
		recipe.ProductName = row.Text(col)
		col++
		recipe.ProductQty = int8(row.Int(col))
		col++

		// Component titles and quantities
		if !row.IsNull(col) {
			recipe.PrimaryBuildCompTitle = row.Text(col)
		}
		col++
		recipe.PrimaryCompQty = int16(row.Int(col))
		col++

		if !row.IsNull(col) {
			recipe.FuelCompTitle = row.Text(col)
		}
		col++
		recipe.FuelCompQty = int16(row.Int(col))
		col++

		// Build component titles and quantities
		if !row.IsNull(col) {
			recipe.Build1CompTitle = row.Text(col)
		}
		col++
		if !row.IsNull(col + 1) {
			recipe.Build1CompQty = int16(row.Int(col + 1))
		}
		col += 2

		if !row.IsNull(col) {
			recipe.Build2CompTitle = row.Text(col)
		}
		col++
		if !row.IsNull(col) {
			recipe.Build2CompQty = int16(row.Int(col))
		}
		col++

		if !row.IsNull(col) {
			recipe.Build3CompTitle = row.Text(col)
		}
		col++
		if !row.IsNull(col) {
			recipe.Build3CompQty = int16(row.Int(col))
		}
		col++

		if !row.IsNull(col) {
			recipe.Build4CompTitle = row.Text(col)
		}
		col++
		if !row.IsNull(col) {
			recipe.Build4CompQty = int16(row.Int(col))
		}
		col++

		// Set tier based on level (C++ logic: level / 10 + 1)
		recipe.Tier = recipe.Level/10 + 1

		// Initialize products for all stages
		for stage := int8(0); stage < 5; stage++ {
			stageID := int32(row.Int(col))
			stageQty := int8(row.Int(col + 5))
			bypassID := int32(row.Int(col + 10))
			bypassQty := int8(row.Int(col + 15))

			recipe.Products[stage] = &RecipeProducts{
				ProductID:    stageID,
				ProductQty:   stageQty,
				ByproductID:  bypassID,
				ByproductQty: bypassQty,
			}
			col++
		}

		if rm.masterRecipeList.AddRecipe(recipe) {
			rm.loadedRecipes[recipe.ID] = recipe
			loadedCount++
		}

		return nil
	})

	if err != nil {
		return fmt.Errorf("failed to load recipes: %w", err)
	}

	// Load recipe components after loading recipes
	if err := rm.loadRecipeComponents(); err != nil {
		return fmt.Errorf("failed to load recipe components: %w", err)
	}

	// Update statistics
	if rm.statisticsEnabled {
		rm.stats.mu.Lock()
		rm.stats.TotalRecipesLoaded = loadedCount
		rm.stats.LoadOperations++
		rm.stats.mu.Unlock()
	}

	return nil
}

// loadRecipeComponents loads component relationships for recipes
func (rm *RecipeManager) loadRecipeComponents() error {
	query := `SELECT r.id, pc.item_id AS primary_comp, fc.item_id AS fuel_comp,
		sc.item_id as secondary_comp, rsc.index + 1 AS slot
		FROM recipe r
		INNER JOIN (select comp_list, item_id FROM recipe_comp_list_item) as pc ON r.primary_comp_list = pc.comp_list
		INNER JOIN (select comp_list, item_id FROM recipe_comp_list_item) as fc ON r.fuel_comp_list = fc.comp_list
		LEFT JOIN recipe_secondary_comp rsc ON rsc.recipe_id = r.id
		LEFT JOIN (select comp_list, item_id FROM recipe_comp_list_item) as sc ON rsc.comp_list = sc.comp_list
		WHERE r.bHaveAllProducts
		ORDER BY r.id, rsc.index ASC`

	var currentRecipeID int32
	var currentRecipe *Recipe

	err := rm.db.Query(query, func(row *database.Row) error {
		recipeID := int32(row.Int(0))
		primaryComp := int32(row.Int(1))
		fuelComp := int32(row.Int(2))

		var secondaryComp int32
		var slot int8

		if !row.IsNull(3) {
			secondaryComp = int32(row.Int(3))
		}
		if !row.IsNull(4) {
			slot = int8(row.Int(4))
		}

		// Get the recipe if it's different from the current one
		if currentRecipeID != recipeID {
			currentRecipeID = recipeID
			currentRecipe = rm.masterRecipeList.GetRecipe(recipeID)
			if currentRecipe == nil {
				return nil
			}
		}

		if currentRecipe != nil && !row.IsNull(3) && !row.IsNull(4) {
			// Add primary component (slot 0)
			if !rm.containsComponent(currentRecipe.Components[0], primaryComp) {
				currentRecipe.AddBuildComponent(primaryComp, 0, false)
			}
			// Add fuel component (slot 5)
			if !rm.containsComponent(currentRecipe.Components[5], fuelComp) {
				currentRecipe.AddBuildComponent(fuelComp, 5, false)
			}
			// Add secondary component to appropriate slot
			if slot >= 1 && slot <= 4 {
				if !rm.containsComponent(currentRecipe.Components[slot], secondaryComp) {
					currentRecipe.AddBuildComponent(secondaryComp, slot, false)
				}
			}
		}

		return nil
	})

	return err
}

// containsComponent checks if a component ID exists in the component slice
func (rm *RecipeManager) containsComponent(components []int32, componentID int32) bool {
	for _, comp := range components {
		if comp == componentID {
			return true
		}
	}
	return false
}

// LoadRecipeBooks loads all recipe books from the database
func (rm *RecipeManager) LoadRecipeBooks() error {
	rm.mu.Lock()
	defer rm.mu.Unlock()

	query := `SELECT id, name, tradeskill_default_level FROM items WHERE item_type='Recipe'`

	loadedCount := int32(0)

	err := rm.db.Query(query, func(row *database.Row) error {
		recipe := NewRecipe()
		recipe.BookID = int32(row.Int(0))
		recipe.BookName = row.Text(1)
		recipe.Level = int8(row.Int(2))

		if rm.masterRecipeBookList.AddRecipeBook(recipe) {
			rm.loadedRecipeBooks[recipe.BookID] = recipe
			loadedCount++
		}

		return nil
	})

	if err != nil {
		return fmt.Errorf("failed to load recipe books: %w", err)
	}

	// Update statistics
	if rm.statisticsEnabled {
		rm.stats.mu.Lock()
		rm.stats.TotalRecipeBooksLoaded = loadedCount
		rm.stats.LoadOperations++
		rm.stats.mu.Unlock()
	}

	return nil
}

// LoadPlayerRecipes loads recipes for a specific player from the database
func (rm *RecipeManager) LoadPlayerRecipes(playerRecipeList *PlayerRecipeList, characterID int32) error {
	rm.mu.RLock()
	defer rm.mu.RUnlock()

	query := `SELECT recipe_id, highest_stage FROM character_recipes WHERE char_id = ?`

	loadedCount := 0

	err := rm.db.Query(query, func(row *database.Row) error {
		recipeID := int32(row.Int(0))
		highestStage := int8(row.Int(1))

		// Get master recipe
		masterRecipe := rm.masterRecipeList.GetRecipe(recipeID)
		if masterRecipe == nil {
			return nil
		}

		// Create player copy of recipe
		playerRecipe := NewRecipeFromRecipe(masterRecipe)
		playerRecipe.HighestStage = highestStage

		if playerRecipeList.AddRecipe(playerRecipe) {
			loadedCount++
		}

		return nil
	}, characterID)

	if err != nil {
		return fmt.Errorf("failed to load player recipes: %w", err)
	}

	// Update statistics
	if rm.statisticsEnabled {
		rm.stats.mu.Lock()
		rm.stats.PlayersWithRecipes++
		rm.stats.LoadOperations++
		rm.stats.mu.Unlock()
	}

	return nil
}

// LoadPlayerRecipeBooks loads recipe books for a specific player from the database
func (rm *RecipeManager) LoadPlayerRecipeBooks(playerRecipeBookList *PlayerRecipeBookList, characterID int32) (int32, error) {
	rm.mu.RLock()
	defer rm.mu.RUnlock()

	query := `SELECT recipebook_id FROM character_recipe_books WHERE char_id = ? ORDER BY recipebook_id`

	count := int32(0)
	var lastID int32

	err := rm.db.Query(query, func(row *database.Row) error {
		recipebookID := int32(row.Int(0))

		// Skip duplicates
		if recipebookID == lastID {
			return nil
		}

		// Create recipe book entry
		recipe := NewRecipe()
		recipe.BookID = recipebookID
		recipe.BookName = fmt.Sprintf("Recipe Book %d", recipebookID) // TODO: Get actual name from items table

		if playerRecipeBookList.AddRecipeBook(recipe) {
			count++
		}
		lastID = recipebookID

		return nil
	}, characterID)

	if err != nil {
		return 0, fmt.Errorf("failed to load player recipe books: %w", err)
	}

	return count, nil
}

// SavePlayerRecipeBook saves a player's recipe book to the database
func (rm *RecipeManager) SavePlayerRecipeBook(characterID int32, recipebookID int32) error {
	query := `INSERT INTO character_recipe_books (char_id, recipebook_id) VALUES (?, ?)`

	err := rm.db.Exec(query, characterID, recipebookID)
	if err != nil {
		return fmt.Errorf("failed to save player recipe book: %w", err)
	}

	// Update statistics
	if rm.statisticsEnabled {
		rm.stats.mu.Lock()
		rm.stats.SaveOperations++
		rm.stats.mu.Unlock()
	}

	return nil
}

// SavePlayerRecipe saves a player's recipe to the database
func (rm *RecipeManager) SavePlayerRecipe(characterID int32, recipeID int32) error {
	query := `INSERT INTO character_recipes (char_id, recipe_id) VALUES (?, ?)`

	err := rm.db.Exec(query, characterID, recipeID)
	if err != nil {
		return fmt.Errorf("failed to save player recipe: %w", err)
	}

	// Update statistics
	if rm.statisticsEnabled {
		rm.stats.mu.Lock()
		rm.stats.SaveOperations++
		rm.stats.mu.Unlock()
	}

	return nil
}

// UpdatePlayerRecipe updates a player's recipe progress in the database
func (rm *RecipeManager) UpdatePlayerRecipe(characterID int32, recipeID int32, highestStage int8) error {
	query := `UPDATE character_recipes SET highest_stage = ? WHERE char_id = ? AND recipe_id = ?`

	err := rm.db.Exec(query, highestStage, characterID, recipeID)
	if err != nil {
		return fmt.Errorf("failed to update player recipe: %w", err)
	}

	// Update statistics
	if rm.statisticsEnabled {
		rm.stats.mu.Lock()
		rm.stats.SaveOperations++
		rm.stats.mu.Unlock()
	}

	return nil
}

// GetMasterRecipeList returns the master recipe list
func (rm *RecipeManager) GetMasterRecipeList() *MasterRecipeList {
	rm.mu.RLock()
	defer rm.mu.RUnlock()
	return rm.masterRecipeList
}

// GetMasterRecipeBookList returns the master recipe book list
func (rm *RecipeManager) GetMasterRecipeBookList() *MasterRecipeBookList {
	rm.mu.RLock()
	defer rm.mu.RUnlock()
	return rm.masterRecipeBookList
}

// GetRecipe retrieves a recipe by ID from the master list
func (rm *RecipeManager) GetRecipe(recipeID int32) *Recipe {
	rm.mu.RLock()
	defer rm.mu.RUnlock()
	return rm.masterRecipeList.GetRecipe(recipeID)
}

// GetRecipeBook retrieves a recipe book by ID
func (rm *RecipeManager) GetRecipeBook(bookID int32) *Recipe {
	rm.mu.RLock()
	defer rm.mu.RUnlock()
	return rm.loadedRecipeBooks[bookID]
}

// GetStatistics returns current recipe system statistics
func (rm *RecipeManager) GetStatistics() RecipeManagerStats {
	if !rm.statisticsEnabled {
		return RecipeManagerStats{}
	}

	rm.stats.mu.RLock()
	defer rm.stats.mu.RUnlock()
	return rm.stats
}

// SetStatisticsEnabled enables or disables statistics collection
func (rm *RecipeManager) SetStatisticsEnabled(enabled bool) {
	rm.mu.Lock()
	defer rm.mu.Unlock()
	rm.statisticsEnabled = enabled
}

// Validate performs comprehensive recipe system validation
func (rm *RecipeManager) Validate() []string {
	rm.mu.RLock()
	defer rm.mu.RUnlock()

	var issues []string

	// Validate master recipe list
	if rm.masterRecipeList == nil {
		issues = append(issues, "master recipe list is nil")
		return issues
	}

	// Validate master recipe book list
	if rm.masterRecipeBookList == nil {
		issues = append(issues, "master recipe book list is nil")
	}

	// Check for recipes with invalid data
	for _, recipe := range rm.loadedRecipes {
		if recipe.ID == 0 {
			issues = append(issues, fmt.Sprintf("recipe has invalid ID: %s", recipe.Name))
		}
		if recipe.Name == "" {
			issues = append(issues, fmt.Sprintf("recipe %d has empty name", recipe.ID))
		}
		if recipe.Level < 0 || recipe.Level > 100 {
			issues = append(issues, fmt.Sprintf("recipe %d has invalid level: %d", recipe.ID, recipe.Level))
		}
		if len(recipe.Products) != 5 {
			issues = append(issues, fmt.Sprintf("recipe %d has invalid products array length: %d", recipe.ID, len(recipe.Products)))
		}
		if len(recipe.Components) != 6 {
			issues = append(issues, fmt.Sprintf("recipe %d has invalid components array length: %d", recipe.ID, len(recipe.Components)))
		}
	}

	return issues
}

// Size returns the total count of loaded recipes
func (rm *RecipeManager) Size() (recipes int32, recipeBooks int32) {
	rm.mu.RLock()
	defer rm.mu.RUnlock()
	return int32(len(rm.loadedRecipes)), int32(len(rm.loadedRecipeBooks))
}
