package collections

import (
	"fmt"
	"strconv"
	"strings"
	"time"
)

// NewCollection creates a new collection instance
func NewCollection() *Collection {
	return &Collection{
		collectionItems:       make([]CollectionItem, 0),
		rewardItems:           make([]CollectionRewardItem, 0),
		selectableRewardItems: make([]CollectionRewardItem, 0),
		lastModified:          time.Now(),
	}
}

// NewCollectionFromData creates a collection from another collection (copy constructor)
func NewCollectionFromData(source *Collection) *Collection {
	if source == nil {
		return nil
	}

	source.mu.RLock()
	defer source.mu.RUnlock()

	collection := &Collection{
		id:                    source.id,
		name:                  source.name,
		category:              source.category,
		level:                 source.level,
		rewardCoin:            source.rewardCoin,
		rewardXP:              source.rewardXP,
		completed:             source.completed,
		saveNeeded:            source.saveNeeded,
		collectionItems:       make([]CollectionItem, len(source.collectionItems)),
		rewardItems:           make([]CollectionRewardItem, len(source.rewardItems)),
		selectableRewardItems: make([]CollectionRewardItem, len(source.selectableRewardItems)),
		lastModified:          time.Now(),
	}

	// Deep copy collection items
	copy(collection.collectionItems, source.collectionItems)

	// Deep copy reward items
	copy(collection.rewardItems, source.rewardItems)

	// Deep copy selectable reward items
	copy(collection.selectableRewardItems, source.selectableRewardItems)

	return collection
}

// SetID sets the collection ID
func (c *Collection) SetID(id int32) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.id = id
}

// SetName sets the collection name
func (c *Collection) SetName(name string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if len(name) > MaxCollectionNameLength {
		name = name[:MaxCollectionNameLength]
	}
	c.name = name
}

// SetCategory sets the collection category
func (c *Collection) SetCategory(category string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if len(category) > MaxCollectionCategoryLength {
		category = category[:MaxCollectionCategoryLength]
	}
	c.category = category
}

// SetLevel sets the collection level
func (c *Collection) SetLevel(level int8) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.level = level
}

// SetCompleted sets the collection completion status
func (c *Collection) SetCompleted(completed bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.completed = completed
	c.lastModified = time.Now()
}

// SetSaveNeeded sets whether the collection needs to be saved
func (c *Collection) SetSaveNeeded(saveNeeded bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.saveNeeded = saveNeeded
}

// SetRewardCoin sets the coin reward amount
func (c *Collection) SetRewardCoin(coin int64) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.rewardCoin = coin
}

// SetRewardXP sets the XP reward amount
func (c *Collection) SetRewardXP(xp int64) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.rewardXP = xp
}

// AddCollectionItem adds a required item to the collection
func (c *Collection) AddCollectionItem(item CollectionItem) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.collectionItems = append(c.collectionItems, item)
}

// AddRewardItem adds a reward item to the collection
func (c *Collection) AddRewardItem(item CollectionRewardItem) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.rewardItems = append(c.rewardItems, item)
}

// AddSelectableRewardItem adds a selectable reward item to the collection
func (c *Collection) AddSelectableRewardItem(item CollectionRewardItem) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.selectableRewardItems = append(c.selectableRewardItems, item)
}

// GetID returns the collection ID
func (c *Collection) GetID() int32 {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.id
}

// GetName returns the collection name
func (c *Collection) GetName() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.name
}

// GetCategory returns the collection category
func (c *Collection) GetCategory() string {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.category
}

// GetLevel returns the collection level
func (c *Collection) GetLevel() int8 {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.level
}

// GetCompleted returns whether the collection is completed
func (c *Collection) GetCompleted() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.completed
}

// GetSaveNeeded returns whether the collection needs to be saved
func (c *Collection) GetSaveNeeded() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.saveNeeded
}

// GetRewardCoin returns the coin reward amount
func (c *Collection) GetRewardCoin() int64 {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.rewardCoin
}

// GetRewardXP returns the XP reward amount
func (c *Collection) GetRewardXP() int64 {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.rewardXP
}

// GetCollectionItems returns a copy of the collection items
func (c *Collection) GetCollectionItems() []CollectionItem {
	c.mu.RLock()
	defer c.mu.RUnlock()
	items := make([]CollectionItem, len(c.collectionItems))
	copy(items, c.collectionItems)
	return items
}

// GetRewardItems returns a copy of the reward items
func (c *Collection) GetRewardItems() []CollectionRewardItem {
	c.mu.RLock()
	defer c.mu.RUnlock()
	items := make([]CollectionRewardItem, len(c.rewardItems))
	copy(items, c.rewardItems)
	return items
}

// GetSelectableRewardItems returns a copy of the selectable reward items
func (c *Collection) GetSelectableRewardItems() []CollectionRewardItem {
	c.mu.RLock()
	defer c.mu.RUnlock()
	items := make([]CollectionRewardItem, len(c.selectableRewardItems))
	copy(items, c.selectableRewardItems)
	return items
}

// NeedsItem checks if the collection needs a specific item
func (c *Collection) NeedsItem(itemID int32) bool {
	c.mu.RLock()
	defer c.mu.RUnlock()

	if c.completed {
		return false
	}

	for _, item := range c.collectionItems {
		if item.ItemID == itemID {
			return item.Found == ItemNotFound
		}
	}

	return false
}

// GetCollectionItemByItemID returns the collection item for a specific item ID
func (c *Collection) GetCollectionItemByItemID(itemID int32) *CollectionItem {
	c.mu.RLock()
	defer c.mu.RUnlock()

	for i := range c.collectionItems {
		if c.collectionItems[i].ItemID == itemID {
			return &c.collectionItems[i]
		}
	}

	return nil
}

// GetIsReadyToTurnIn checks if all required items have been found
func (c *Collection) GetIsReadyToTurnIn() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()

	if c.completed {
		return false
	}

	for _, item := range c.collectionItems {
		if item.Found == ItemNotFound {
			return false
		}
	}

	return true
}

// MarkItemFound marks an item as found in the collection
func (c *Collection) MarkItemFound(itemID int32) bool {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.completed {
		return false
	}

	for i := range c.collectionItems {
		if c.collectionItems[i].ItemID == itemID && c.collectionItems[i].Found == ItemNotFound {
			c.collectionItems[i].Found = ItemFound
			c.saveNeeded = true
			c.lastModified = time.Now()
			return true
		}
	}

	return false
}

// GetProgress returns the completion progress as a percentage
func (c *Collection) GetProgress() float64 {
	c.mu.RLock()
	defer c.mu.RUnlock()

	if len(c.collectionItems) == 0 {
		return 0.0
	}

	foundCount := 0
	for _, item := range c.collectionItems {
		if item.Found == ItemFound {
			foundCount++
		}
	}

	return float64(foundCount) / float64(len(c.collectionItems)) * 100.0
}

// GetFoundItemsCount returns the number of found items
func (c *Collection) GetFoundItemsCount() int {
	c.mu.RLock()
	defer c.mu.RUnlock()

	count := 0
	for _, item := range c.collectionItems {
		if item.Found == ItemFound {
			count++
		}
	}
	return count
}

// GetTotalItemsCount returns the total number of required items
func (c *Collection) GetTotalItemsCount() int {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return len(c.collectionItems)
}

// GetCollectionInfo returns detailed collection information
func (c *Collection) GetCollectionInfo() CollectionInfo {
	c.mu.RLock()
	defer c.mu.RUnlock()

	return CollectionInfo{
		ID:                c.id,
		Name:              c.name,
		Category:          c.category,
		Level:             c.level,
		Completed:         c.completed,
		ReadyToTurnIn:     c.getIsReadyToTurnInNoLock(),
		ItemsFound:        c.getFoundItemsCountNoLock(),
		ItemsTotal:        len(c.collectionItems),
		RewardCoin:        c.rewardCoin,
		RewardXP:          c.rewardXP,
		RewardItems:       append([]CollectionRewardItem(nil), c.rewardItems...),
		SelectableRewards: append([]CollectionRewardItem(nil), c.selectableRewardItems...),
		RequiredItems:     append([]CollectionItem(nil), c.collectionItems...),
	}
}

// GetCollectionProgress returns detailed progress information
func (c *Collection) GetCollectionProgress() CollectionProgress {
	c.mu.RLock()
	defer c.mu.RUnlock()

	var foundItems, neededItems []CollectionItem
	for _, item := range c.collectionItems {
		if item.Found == ItemFound {
			foundItems = append(foundItems, item)
		} else {
			neededItems = append(neededItems, item)
		}
	}

	return CollectionProgress{
		CollectionID:  c.id,
		Name:          c.name,
		Category:      c.category,
		Level:         c.level,
		Completed:     c.completed,
		ReadyToTurnIn: c.getIsReadyToTurnInNoLock(),
		Progress:      c.getProgressNoLock(),
		ItemsFound:    foundItems,
		ItemsNeeded:   neededItems,
		LastUpdated:   c.lastModified,
	}
}

// LoadFromRewardData loads reward data into the collection
func (c *Collection) LoadFromRewardData(rewards []CollectionRewardData) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	for _, reward := range rewards {
		switch strings.ToLower(reward.RewardType) {
		case strings.ToLower(RewardTypeItem):
			itemID, err := strconv.ParseInt(reward.RewardValue, 10, 32)
			if err != nil {
				return fmt.Errorf("invalid item ID in reward: %s", reward.RewardValue)
			}
			c.rewardItems = append(c.rewardItems, CollectionRewardItem{
				ItemID:   int32(itemID),
				Quantity: reward.Quantity,
			})

		case strings.ToLower(RewardTypeSelectable):
			itemID, err := strconv.ParseInt(reward.RewardValue, 10, 32)
			if err != nil {
				return fmt.Errorf("invalid item ID in selectable reward: %s", reward.RewardValue)
			}
			c.selectableRewardItems = append(c.selectableRewardItems, CollectionRewardItem{
				ItemID:   int32(itemID),
				Quantity: reward.Quantity,
			})

		case strings.ToLower(RewardTypeCoin):
			coin, err := strconv.ParseInt(reward.RewardValue, 10, 64)
			if err != nil {
				return fmt.Errorf("invalid coin amount in reward: %s", reward.RewardValue)
			}
			c.rewardCoin = coin

		case strings.ToLower(RewardTypeXP):
			xp, err := strconv.ParseInt(reward.RewardValue, 10, 64)
			if err != nil {
				return fmt.Errorf("invalid XP amount in reward: %s", reward.RewardValue)
			}
			c.rewardXP = xp

		default:
			return fmt.Errorf("unknown reward type: %s", reward.RewardType)
		}
	}

	return nil
}

// Validate checks if the collection data is valid
func (c *Collection) Validate() error {
	c.mu.RLock()
	defer c.mu.RUnlock()

	if c.id <= 0 {
		return fmt.Errorf("collection ID must be positive")
	}

	if strings.TrimSpace(c.name) == "" {
		return fmt.Errorf("collection name cannot be empty")
	}

	if len(c.collectionItems) == 0 {
		return fmt.Errorf("collection must have at least one required item")
	}

	// Check for duplicate item IDs
	itemIDs := make(map[int32]bool)
	for _, item := range c.collectionItems {
		if itemIDs[item.ItemID] {
			return fmt.Errorf("duplicate item ID in collection: %d", item.ItemID)
		}
		itemIDs[item.ItemID] = true

		if item.ItemID <= 0 {
			return fmt.Errorf("collection item ID must be positive: %d", item.ItemID)
		}
	}

	return nil
}

// Helper methods (no lock versions for internal use)

func (c *Collection) getIsReadyToTurnInNoLock() bool {
	if c.completed {
		return false
	}

	for _, item := range c.collectionItems {
		if item.Found == ItemNotFound {
			return false
		}
	}

	return true
}

func (c *Collection) getFoundItemsCountNoLock() int {
	count := 0
	for _, item := range c.collectionItems {
		if item.Found == ItemFound {
			count++
		}
	}
	return count
}

func (c *Collection) getProgressNoLock() float64 {
	if len(c.collectionItems) == 0 {
		return 0.0
	}

	foundCount := c.getFoundItemsCountNoLock()
	return float64(foundCount) / float64(len(c.collectionItems)) * 100.0
}
