package collections

import (
	"context"
	"fmt"
	"sync"
)

// NewCollectionManager creates a new collection manager instance
func NewCollectionManager(database CollectionDatabase, itemLookup ItemLookup) *CollectionManager {
	return &CollectionManager{
		masterList: NewMasterCollectionList(database),
		database:   database,
		itemLookup: itemLookup,
	}
}

// Initialize initializes the collection system by loading all collections
func (cm *CollectionManager) Initialize(ctx context.Context) error {
	return cm.masterList.Initialize(ctx, cm.itemLookup)
}

// GetMasterList returns the master collection list
func (cm *CollectionManager) GetMasterList() *MasterCollectionList {
	return cm.masterList
}

// CreatePlayerCollectionList creates a new player collection list
func (cm *CollectionManager) CreatePlayerCollectionList(characterID int32) *PlayerCollectionList {
	return NewPlayerCollectionList(characterID, cm.database)
}

// GetCollection returns a collection by ID from the master list
func (cm *CollectionManager) GetCollection(collectionID int32) *Collection {
	return cm.masterList.GetCollection(collectionID)
}

// GetCollectionCopy returns a copy of a collection by ID
func (cm *CollectionManager) GetCollectionCopy(collectionID int32) *Collection {
	return cm.masterList.GetCollectionCopy(collectionID)
}

// ProcessItemFound processes when a player finds an item across all collections
func (cm *CollectionManager) ProcessItemFound(playerList *PlayerCollectionList, itemID int32) ([]*Collection, error) {
	if playerList == nil {
		return nil, fmt.Errorf("player collection list is nil")
	}

	return playerList.ProcessItemFound(itemID, cm.masterList)
}

// CompleteCollection processes collection completion for a player
func (cm *CollectionManager) CompleteCollection(playerList *PlayerCollectionList, collectionID int32, rewardProvider RewardProvider) error {
	if playerList == nil {
		return fmt.Errorf("player collection list is nil")
	}

	collection := playerList.GetCollection(collectionID)
	if collection == nil {
		return fmt.Errorf("collection %d not found in player list", collectionID)
	}

	if !collection.GetIsReadyToTurnIn() {
		return fmt.Errorf("collection %d is not ready to complete", collectionID)
	}

	// Give rewards if provider is available
	if rewardProvider != nil {
		characterID := playerList.GetCharacterID()

		// Give coin reward
		if coin := collection.GetRewardCoin(); coin > 0 {
			if err := rewardProvider.GiveCoin(characterID, coin); err != nil {
				return fmt.Errorf("failed to give coin reward: %w", err)
			}
		}

		// Give XP reward
		if xp := collection.GetRewardXP(); xp > 0 {
			if err := rewardProvider.GiveXP(characterID, xp); err != nil {
				return fmt.Errorf("failed to give XP reward: %w", err)
			}
		}

		// Give item rewards
		for _, reward := range collection.GetRewardItems() {
			if err := rewardProvider.GiveItem(characterID, reward.ItemID, reward.Quantity); err != nil {
				return fmt.Errorf("failed to give item reward %d: %w", reward.ItemID, err)
			}
		}
	}

	// Mark collection as completed
	return playerList.CompleteCollection(collectionID)
}

// GetAvailableCollections returns collections available to a player based on level
func (cm *CollectionManager) GetAvailableCollections(playerLevel int8) []*Collection {
	return cm.masterList.GetCollectionsByLevel(0, playerLevel)
}

// GetCollectionsByCategory returns collections in a specific category
func (cm *CollectionManager) GetCollectionsByCategory(category string) []*Collection {
	return cm.masterList.GetCollectionsByCategory(category)
}

// GetAllCategories returns all collection categories
func (cm *CollectionManager) GetAllCategories() []string {
	return cm.masterList.GetCategories()
}

// SearchCollections searches for collections by name
func (cm *CollectionManager) SearchCollections(searchTerm string) []*Collection {
	return cm.masterList.FindCollectionsByName(searchTerm)
}

// ValidateSystemIntegrity validates the integrity of the collection system
func (cm *CollectionManager) ValidateSystemIntegrity() []error {
	return cm.masterList.ValidateIntegrity(cm.itemLookup)
}

// GetSystemStatistics returns overall collection system statistics
func (cm *CollectionManager) GetSystemStatistics() CollectionStatistics {
	return cm.masterList.GetStatistics()
}

// CollectionService provides high-level collection system services
type CollectionService struct {
	manager       *CollectionManager
	playerLists   map[int32]*PlayerCollectionList
	mu            sync.RWMutex
	clientManager ClientManager
	eventHandler  CollectionEventHandler
	logger        LogHandler
}

// NewCollectionService creates a new collection service
func NewCollectionService(database CollectionDatabase, itemLookup ItemLookup, clientManager ClientManager) *CollectionService {
	return &CollectionService{
		manager:       NewCollectionManager(database, itemLookup),
		playerLists:   make(map[int32]*PlayerCollectionList),
		clientManager: clientManager,
	}
}

// SetEventHandler sets the collection event handler
func (cs *CollectionService) SetEventHandler(handler CollectionEventHandler) {
	cs.eventHandler = handler
}

// SetLogger sets the logger for the service
func (cs *CollectionService) SetLogger(logger LogHandler) {
	cs.logger = logger
}

// Initialize initializes the collection service
func (cs *CollectionService) Initialize(ctx context.Context) error {
	return cs.manager.Initialize(ctx)
}

// LoadPlayerCollections loads collections for a specific player
func (cs *CollectionService) LoadPlayerCollections(ctx context.Context, characterID int32) error {
	cs.mu.Lock()
	defer cs.mu.Unlock()

	playerList := cs.manager.CreatePlayerCollectionList(characterID)
	if err := playerList.Initialize(ctx, cs.manager.GetMasterList()); err != nil {
		return fmt.Errorf("failed to initialize player collections: %w", err)
	}

	cs.playerLists[characterID] = playerList

	if cs.logger != nil {
		cs.logger.LogDebug("collections", "Loaded %d collections for character %d",
			playerList.Size(), characterID)
	}

	return nil
}

// UnloadPlayerCollections unloads collections for a player (when they log out)
func (cs *CollectionService) UnloadPlayerCollections(ctx context.Context, characterID int32) error {
	cs.mu.Lock()
	defer cs.mu.Unlock()

	playerList, exists := cs.playerLists[characterID]
	if !exists {
		return nil // Already unloaded
	}

	// Save any pending changes
	if err := playerList.SaveCollections(ctx); err != nil {
		if cs.logger != nil {
			cs.logger.LogError("collections", "Failed to save collections for character %d: %v", characterID, err)
		}
		return fmt.Errorf("failed to save collections: %w", err)
	}

	delete(cs.playerLists, characterID)

	if cs.logger != nil {
		cs.logger.LogDebug("collections", "Unloaded collections for character %d", characterID)
	}

	return nil
}

// ProcessItemFound processes when a player finds an item
func (cs *CollectionService) ProcessItemFound(characterID, itemID int32) error {
	cs.mu.RLock()
	playerList, exists := cs.playerLists[characterID]
	cs.mu.RUnlock()

	if !exists {
		return fmt.Errorf("player collections not loaded for character %d", characterID)
	}

	updatedCollections, err := cs.manager.ProcessItemFound(playerList, itemID)
	if err != nil {
		return fmt.Errorf("failed to process found item: %w", err)
	}

	// Notify client and event handler of updates
	for _, collection := range updatedCollections {
		if cs.clientManager != nil {
			cs.clientManager.SendCollectionUpdate(characterID, collection)
		}

		if cs.eventHandler != nil {
			cs.eventHandler.OnItemFound(characterID, collection.GetID(), itemID)
		}

		if cs.logger != nil {
			cs.logger.LogDebug("collections", "Character %d found item %d for collection %d (%s)",
				characterID, itemID, collection.GetID(), collection.GetName())
		}
	}

	return nil
}

// CompleteCollection processes collection completion
func (cs *CollectionService) CompleteCollection(characterID, collectionID int32, rewardProvider RewardProvider) error {
	cs.mu.RLock()
	playerList, exists := cs.playerLists[characterID]
	cs.mu.RUnlock()

	if !exists {
		return fmt.Errorf("player collections not loaded for character %d", characterID)
	}

	collection := playerList.GetCollection(collectionID)
	if collection == nil {
		return fmt.Errorf("collection %d not found for character %d", collectionID, characterID)
	}

	// Complete the collection
	if err := cs.manager.CompleteCollection(playerList, collectionID, rewardProvider); err != nil {
		return fmt.Errorf("failed to complete collection: %w", err)
	}

	// Notify client and event handler
	if cs.clientManager != nil {
		cs.clientManager.SendCollectionComplete(characterID, collection)
	}

	if cs.eventHandler != nil {
		rewards := collection.GetRewardItems()
		selectableRewards := collection.GetSelectableRewardItems()
		allRewards := append(rewards, selectableRewards...)
		cs.eventHandler.OnCollectionCompleted(characterID, collectionID)
		cs.eventHandler.OnRewardClaimed(characterID, collectionID, allRewards,
			collection.GetRewardCoin(), collection.GetRewardXP())
	}

	if cs.logger != nil {
		cs.logger.LogInfo("collections", "Character %d completed collection %d (%s)",
			characterID, collectionID, collection.GetName())
	}

	return nil
}

// GetPlayerCollections returns all collections for a player
func (cs *CollectionService) GetPlayerCollections(characterID int32) ([]*Collection, error) {
	cs.mu.RLock()
	playerList, exists := cs.playerLists[characterID]
	cs.mu.RUnlock()

	if !exists {
		return nil, fmt.Errorf("player collections not loaded for character %d", characterID)
	}

	return playerList.GetAllCollections(), nil
}

// GetPlayerCollectionProgress returns detailed progress for all player collections
func (cs *CollectionService) GetPlayerCollectionProgress(characterID int32) ([]CollectionProgress, error) {
	cs.mu.RLock()
	playerList, exists := cs.playerLists[characterID]
	cs.mu.RUnlock()

	if !exists {
		return nil, fmt.Errorf("player collections not loaded for character %d", characterID)
	}

	return playerList.GetCollectionProgress(), nil
}

// SendCollectionList sends available collections to a player
func (cs *CollectionService) SendCollectionList(characterID int32, playerLevel int8) error {
	if cs.clientManager == nil {
		return fmt.Errorf("client manager not available")
	}

	collections := cs.manager.GetAvailableCollections(playerLevel)
	collectionInfos := make([]CollectionInfo, len(collections))

	for i, collection := range collections {
		collectionInfos[i] = collection.GetCollectionInfo()
	}

	return cs.clientManager.SendCollectionList(characterID, collectionInfos)
}

// SaveAllPlayerCollections saves all loaded player collections
func (cs *CollectionService) SaveAllPlayerCollections(ctx context.Context) error {
	cs.mu.RLock()
	playerLists := make(map[int32]*PlayerCollectionList)
	for characterID, playerList := range cs.playerLists {
		playerLists[characterID] = playerList
	}
	cs.mu.RUnlock()

	var saveErrors []error
	for characterID, playerList := range playerLists {
		if err := playerList.SaveCollections(ctx); err != nil {
			saveErrors = append(saveErrors, fmt.Errorf("character %d: %w", characterID, err))
			if cs.logger != nil {
				cs.logger.LogError("collections", "Failed to save collections for character %d: %v", characterID, err)
			}
		}
	}

	if len(saveErrors) > 0 {
		return fmt.Errorf("failed to save some player collections: %v", saveErrors)
	}

	return nil
}

// GetLoadedPlayerCount returns the number of players with loaded collections
func (cs *CollectionService) GetLoadedPlayerCount() int {
	cs.mu.RLock()
	defer cs.mu.RUnlock()
	return len(cs.playerLists)
}

// IsPlayerLoaded checks if a player's collections are loaded
func (cs *CollectionService) IsPlayerLoaded(characterID int32) bool {
	cs.mu.RLock()
	defer cs.mu.RUnlock()
	_, exists := cs.playerLists[characterID]
	return exists
}

// GetMasterCollection returns a collection from the master list
func (cs *CollectionService) GetMasterCollection(collectionID int32) *Collection {
	return cs.manager.GetCollection(collectionID)
}

// GetAllCategories returns all collection categories
func (cs *CollectionService) GetAllCategories() []string {
	return cs.manager.GetAllCategories()
}

// SearchCollections searches for collections by name
func (cs *CollectionService) SearchCollections(searchTerm string) []*Collection {
	return cs.manager.SearchCollections(searchTerm)
}
