package collections import ( "context" "fmt" "eq2emu/internal/database" ) // DatabaseCollectionManager implements CollectionDatabase interface using the existing database wrapper type DatabaseCollectionManager struct { db *database.DB } // NewDatabaseCollectionManager creates a new database collection manager func NewDatabaseCollectionManager(db *database.DB) *DatabaseCollectionManager { return &DatabaseCollectionManager{ db: db, } } // LoadCollections retrieves all collections from database func (dcm *DatabaseCollectionManager) LoadCollections(ctx context.Context) ([]CollectionData, error) { query := "SELECT `id`, `collection_name`, `collection_category`, `level` FROM `collections`" var collections []CollectionData err := dcm.db.Query(query, func(row *database.Row) error { var collection CollectionData collection.ID = int32(row.Int(0)) collection.Name = row.Text(1) collection.Category = row.Text(2) collection.Level = int8(row.Int(3)) collections = append(collections, collection) return nil }) if err != nil { return nil, fmt.Errorf("failed to query collections: %w", err) } return collections, nil } // LoadCollectionItems retrieves items for a specific collection func (dcm *DatabaseCollectionManager) LoadCollectionItems(ctx context.Context, collectionID int32) ([]CollectionItem, error) { query := `SELECT item_id, item_index FROM collection_details WHERE collection_id = ? ORDER BY item_index ASC` var items []CollectionItem err := dcm.db.Query(query, func(row *database.Row) error { var item CollectionItem item.ItemID = int32(row.Int(0)) item.Index = int8(row.Int(1)) // Items start as not found item.Found = ItemNotFound items = append(items, item) return nil }, collectionID) if err != nil { return nil, fmt.Errorf("failed to query collection items for collection %d: %w", collectionID, err) } return items, nil } // LoadCollectionRewards retrieves rewards for a specific collection func (dcm *DatabaseCollectionManager) LoadCollectionRewards(ctx context.Context, collectionID int32) ([]CollectionRewardData, error) { query := `SELECT collection_id, reward_type, reward_value, reward_quantity FROM collection_rewards WHERE collection_id = ?` var rewards []CollectionRewardData err := dcm.db.Query(query, func(row *database.Row) error { var reward CollectionRewardData reward.CollectionID = int32(row.Int(0)) reward.RewardType = row.Text(1) reward.RewardValue = row.Text(2) reward.Quantity = int8(row.Int(3)) rewards = append(rewards, reward) return nil }, collectionID) if err != nil { return nil, fmt.Errorf("failed to query collection rewards for collection %d: %w", collectionID, err) } return rewards, nil } // LoadPlayerCollections retrieves player's collection progress func (dcm *DatabaseCollectionManager) LoadPlayerCollections(ctx context.Context, characterID int32) ([]PlayerCollectionData, error) { query := `SELECT char_id, collection_id, completed FROM character_collections WHERE char_id = ?` var collections []PlayerCollectionData err := dcm.db.Query(query, func(row *database.Row) error { var collection PlayerCollectionData collection.CharacterID = int32(row.Int(0)) collection.CollectionID = int32(row.Int(1)) collection.Completed = row.Bool(2) collections = append(collections, collection) return nil }, characterID) if err != nil { return nil, fmt.Errorf("failed to query player collections for character %d: %w", characterID, err) } return collections, nil } // LoadPlayerCollectionItems retrieves player's found collection items func (dcm *DatabaseCollectionManager) LoadPlayerCollectionItems(ctx context.Context, characterID, collectionID int32) ([]int32, error) { query := `SELECT collection_item_id FROM character_collection_items WHERE char_id = ? AND collection_id = ?` var itemIDs []int32 err := dcm.db.Query(query, func(row *database.Row) error { itemID := int32(row.Int(0)) itemIDs = append(itemIDs, itemID) return nil }, characterID, collectionID) if err != nil { return nil, fmt.Errorf("failed to query player collection items for character %d, collection %d: %w", characterID, collectionID, err) } return itemIDs, nil } // SavePlayerCollection saves player collection completion status func (dcm *DatabaseCollectionManager) SavePlayerCollection(ctx context.Context, characterID, collectionID int32, completed bool) error { completedInt := 0 if completed { completedInt = 1 } query := `INSERT INTO character_collections (char_id, collection_id, completed) VALUES (?, ?, ?) ON CONFLICT(char_id, collection_id) DO UPDATE SET completed = ?` err := dcm.db.Exec(query, characterID, collectionID, completedInt, completedInt) if err != nil { return fmt.Errorf("failed to save player collection for character %d, collection %d: %w", characterID, collectionID, err) } return nil } // SavePlayerCollectionItem saves a found collection item func (dcm *DatabaseCollectionManager) SavePlayerCollectionItem(ctx context.Context, characterID, collectionID, itemID int32) error { query := `INSERT OR IGNORE INTO character_collection_items (char_id, collection_id, collection_item_id) VALUES (?, ?, ?)` err := dcm.db.Exec(query, characterID, collectionID, itemID) if err != nil { return fmt.Errorf("failed to save player collection item for character %d, collection %d, item %d: %w", characterID, collectionID, itemID, err) } return nil } // SavePlayerCollections saves all modified player collections func (dcm *DatabaseCollectionManager) SavePlayerCollections(ctx context.Context, characterID int32, collections []*Collection) error { if len(collections) == 0 { return nil } // Use a transaction for atomic updates err := dcm.db.Transaction(func(db *database.DB) error { for _, collection := range collections { if !collection.GetSaveNeeded() { continue } // Save collection completion status if err := dcm.savePlayerCollectionInTx(db, characterID, collection); err != nil { return fmt.Errorf("failed to save collection %d: %w", collection.GetID(), err) } // Save found items if err := dcm.savePlayerCollectionItemsInTx(db, characterID, collection); err != nil { return fmt.Errorf("failed to save collection items for collection %d: %w", collection.GetID(), err) } } return nil }) if err != nil { return fmt.Errorf("transaction failed: %w", err) } return nil } // savePlayerCollectionInTx saves a single collection within a transaction func (dcm *DatabaseCollectionManager) savePlayerCollectionInTx(db *database.DB, characterID int32, collection *Collection) error { completedInt := 0 if collection.GetCompleted() { completedInt = 1 } query := `INSERT INTO character_collections (char_id, collection_id, completed) VALUES (?, ?, ?) ON CONFLICT(char_id, collection_id) DO UPDATE SET completed = ?` err := db.Exec(query, characterID, collection.GetID(), completedInt, completedInt) return err } // savePlayerCollectionItemsInTx saves collection items within a transaction func (dcm *DatabaseCollectionManager) savePlayerCollectionItemsInTx(db *database.DB, characterID int32, collection *Collection) error { items := collection.GetCollectionItems() for _, item := range items { if item.Found == ItemFound { query := `INSERT OR IGNORE INTO character_collection_items (char_id, collection_id, collection_item_id) VALUES (?, ?, ?)` err := db.Exec(query, characterID, collection.GetID(), item.ItemID) if err != nil { return fmt.Errorf("failed to save item %d: %w", item.ItemID, err) } } } return nil } // EnsureCollectionTables creates the collection tables if they don't exist func (dcm *DatabaseCollectionManager) EnsureCollectionTables(ctx context.Context) error { queries := []string{ `CREATE TABLE IF NOT EXISTS collections ( id INTEGER PRIMARY KEY, collection_name TEXT NOT NULL, collection_category TEXT NOT NULL DEFAULT '', level INTEGER NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP )`, `CREATE TABLE IF NOT EXISTS collection_details ( collection_id INTEGER NOT NULL, item_id INTEGER NOT NULL, item_index INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (collection_id, item_id), FOREIGN KEY (collection_id) REFERENCES collections(id) ON DELETE CASCADE )`, `CREATE TABLE IF NOT EXISTS collection_rewards ( id INTEGER PRIMARY KEY AUTOINCREMENT, collection_id INTEGER NOT NULL, reward_type TEXT NOT NULL, reward_value TEXT NOT NULL, reward_quantity INTEGER NOT NULL DEFAULT 1, FOREIGN KEY (collection_id) REFERENCES collections(id) ON DELETE CASCADE )`, `CREATE TABLE IF NOT EXISTS character_collections ( char_id INTEGER NOT NULL, collection_id INTEGER NOT NULL, completed INTEGER NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (char_id, collection_id), FOREIGN KEY (collection_id) REFERENCES collections(id) ON DELETE CASCADE )`, `CREATE TABLE IF NOT EXISTS character_collection_items ( char_id INTEGER NOT NULL, collection_id INTEGER NOT NULL, collection_item_id INTEGER NOT NULL, found_at DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (char_id, collection_id, collection_item_id), FOREIGN KEY (char_id, collection_id) REFERENCES character_collections(char_id, collection_id) ON DELETE CASCADE )`, } for i, query := range queries { err := dcm.db.Exec(query) if err != nil { return fmt.Errorf("failed to create collection table %d: %w", i+1, err) } } // Create indexes for better performance indexes := []string{ `CREATE INDEX IF NOT EXISTS idx_collection_details_collection_id ON collection_details(collection_id)`, `CREATE INDEX IF NOT EXISTS idx_collection_rewards_collection_id ON collection_rewards(collection_id)`, `CREATE INDEX IF NOT EXISTS idx_character_collections_char_id ON character_collections(char_id)`, `CREATE INDEX IF NOT EXISTS idx_character_collection_items_char_id ON character_collection_items(char_id)`, `CREATE INDEX IF NOT EXISTS idx_character_collection_items_collection_id ON character_collection_items(collection_id)`, `CREATE INDEX IF NOT EXISTS idx_collections_category ON collections(collection_category)`, `CREATE INDEX IF NOT EXISTS idx_collections_level ON collections(level)`, } for i, query := range indexes { err := dcm.db.Exec(query) if err != nil { return fmt.Errorf("failed to create collection index %d: %w", i+1, err) } } return nil } // GetCollectionCount returns the total number of collections in the database func (dcm *DatabaseCollectionManager) GetCollectionCount(ctx context.Context) (int, error) { query := "SELECT COUNT(*) FROM collections" row, err := dcm.db.QueryRow(query) if err != nil { return 0, fmt.Errorf("failed to get collection count: %w", err) } defer row.Close() if row == nil { return 0, nil } return row.Int(0), nil } // GetPlayerCollectionCount returns the number of collections a player has func (dcm *DatabaseCollectionManager) GetPlayerCollectionCount(ctx context.Context, characterID int32) (int, error) { query := "SELECT COUNT(*) FROM character_collections WHERE char_id = ?" row, err := dcm.db.QueryRow(query, characterID) if err != nil { return 0, fmt.Errorf("failed to get player collection count for character %d: %w", characterID, err) } defer row.Close() if row == nil { return 0, nil } return row.Int(0), nil } // GetCompletedCollectionCount returns the number of completed collections for a player func (dcm *DatabaseCollectionManager) GetCompletedCollectionCount(ctx context.Context, characterID int32) (int, error) { query := "SELECT COUNT(*) FROM character_collections WHERE char_id = ? AND completed = 1" row, err := dcm.db.QueryRow(query, characterID) if err != nil { return 0, fmt.Errorf("failed to get completed collection count for character %d: %w", characterID, err) } defer row.Close() if row == nil { return 0, nil } return row.Int(0), nil } // DeletePlayerCollection removes a player's collection progress func (dcm *DatabaseCollectionManager) DeletePlayerCollection(ctx context.Context, characterID, collectionID int32) error { // Use a transaction to ensure both tables are updated atomically err := dcm.db.Transaction(func(db *database.DB) error { // Delete collection items first due to foreign key constraint err := db.Exec( "DELETE FROM character_collection_items WHERE char_id = ? AND collection_id = ?", characterID, collectionID) if err != nil { return fmt.Errorf("failed to delete player collection items: %w", err) } // Delete collection err = db.Exec( "DELETE FROM character_collections WHERE char_id = ? AND collection_id = ?", characterID, collectionID) if err != nil { return fmt.Errorf("failed to delete player collection: %w", err) } return nil }) if err != nil { return fmt.Errorf("transaction failed: %w", err) } return nil } // GetCollectionStatistics returns database-level collection statistics func (dcm *DatabaseCollectionManager) GetCollectionStatistics(ctx context.Context) (CollectionStatistics, error) { var stats CollectionStatistics // Total collections row, err := dcm.db.QueryRow("SELECT COUNT(*) FROM collections") if err != nil { return stats, fmt.Errorf("failed to get total collections: %w", err) } if row != nil { stats.TotalCollections = row.Int(0) row.Close() } // Total collection items row, err = dcm.db.QueryRow("SELECT COUNT(*) FROM collection_details") if err != nil { return stats, fmt.Errorf("failed to get total items: %w", err) } if row != nil { stats.TotalItems = row.Int(0) row.Close() } // Players with collections row, err = dcm.db.QueryRow("SELECT COUNT(DISTINCT char_id) FROM character_collections") if err != nil { return stats, fmt.Errorf("failed to get players with collections: %w", err) } if row != nil { stats.PlayersWithCollections = row.Int(0) row.Close() } // Completed collections across all players row, err = dcm.db.QueryRow("SELECT COUNT(*) FROM character_collections WHERE completed = 1") if err != nil { return stats, fmt.Errorf("failed to get completed collections: %w", err) } if row != nil { stats.CompletedCollections = row.Int(0) row.Close() } // Active collections (incomplete with at least one item found) across all players query := `SELECT COUNT(DISTINCT cc.char_id || '-' || cc.collection_id) FROM character_collections cc JOIN character_collection_items cci ON cc.char_id = cci.char_id AND cc.collection_id = cci.collection_id WHERE cc.completed = 0` row, err = dcm.db.QueryRow(query) if err != nil { return stats, fmt.Errorf("failed to get active collections: %w", err) } if row != nil { stats.ActiveCollections = row.Int(0) row.Close() } // Found items across all players row, err = dcm.db.QueryRow("SELECT COUNT(*) FROM character_collection_items") if err != nil { return stats, fmt.Errorf("failed to get found items: %w", err) } if row != nil { stats.FoundItems = row.Int(0) row.Close() } // Total rewards row, err = dcm.db.QueryRow("SELECT COUNT(*) FROM collection_rewards") if err != nil { return stats, fmt.Errorf("failed to get total rewards: %w", err) } if row != nil { stats.TotalRewards = row.Int(0) row.Close() } return stats, nil }