package collections

import (
	"context"
	"fmt"

	"zombiezen.com/go/sqlite"
	"zombiezen.com/go/sqlite/sqlitex"
)

// DatabaseCollectionManager implements CollectionDatabase interface using sqlitex.Pool
type DatabaseCollectionManager struct {
	pool *sqlitex.Pool
}

// NewDatabaseCollectionManager creates a new database collection manager
func NewDatabaseCollectionManager(pool *sqlitex.Pool) *DatabaseCollectionManager {
	return &DatabaseCollectionManager{
		pool: pool,
	}
}

// LoadCollections retrieves all collections from database
func (dcm *DatabaseCollectionManager) LoadCollections(ctx context.Context) ([]CollectionData, error) {
	conn, err := dcm.pool.Take(context.Background())
	if err != nil {
		return nil, fmt.Errorf("failed to get connection: %w", err)
	}
	defer dcm.pool.Put(conn)

	query := "SELECT `id`, `collection_name`, `collection_category`, `level` FROM `collections`"

	var collections []CollectionData
	err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
		ResultFunc: func(stmt *sqlite.Stmt) error {
			var collection CollectionData
			collection.ID = int32(stmt.ColumnInt64(0))
			collection.Name = stmt.ColumnText(1)
			collection.Category = stmt.ColumnText(2)
			collection.Level = int8(stmt.ColumnInt64(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) {
	conn, err := dcm.pool.Take(context.Background())
	if err != nil {
		return nil, fmt.Errorf("failed to get connection: %w", err)
	}
	defer dcm.pool.Put(conn)

	query := `SELECT item_id, item_index
			  FROM collection_details
			  WHERE collection_id = ?
			  ORDER BY item_index ASC`

	var items []CollectionItem
	err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
		Args: []any{collectionID},
		ResultFunc: func(stmt *sqlite.Stmt) error {
			var item CollectionItem
			item.ItemID = int32(stmt.ColumnInt64(0))
			item.Index = int8(stmt.ColumnInt64(1))
			// Items start as not found
			item.Found = ItemNotFound
			items = append(items, item)
			return nil
		},
	})

	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) {
	conn, err := dcm.pool.Take(context.Background())
	if err != nil {
		return nil, fmt.Errorf("failed to get connection: %w", err)
	}
	defer dcm.pool.Put(conn)

	query := `SELECT collection_id, reward_type, reward_value, reward_quantity
			  FROM collection_rewards
			  WHERE collection_id = ?`

	var rewards []CollectionRewardData
	err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
		Args: []any{collectionID},
		ResultFunc: func(stmt *sqlite.Stmt) error {
			var reward CollectionRewardData
			reward.CollectionID = int32(stmt.ColumnInt64(0))
			reward.RewardType = stmt.ColumnText(1)
			reward.RewardValue = stmt.ColumnText(2)
			reward.Quantity = int8(stmt.ColumnInt64(3))
			rewards = append(rewards, reward)
			return nil
		},
	})

	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) {
	conn, err := dcm.pool.Take(context.Background())
	if err != nil {
		return nil, fmt.Errorf("failed to get connection: %w", err)
	}
	defer dcm.pool.Put(conn)

	query := `SELECT char_id, collection_id, completed
			  FROM character_collections
			  WHERE char_id = ?`

	var collections []PlayerCollectionData
	err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
		Args: []any{characterID},
		ResultFunc: func(stmt *sqlite.Stmt) error {
			var collection PlayerCollectionData
			collection.CharacterID = int32(stmt.ColumnInt64(0))
			collection.CollectionID = int32(stmt.ColumnInt64(1))
			collection.Completed = stmt.ColumnInt64(2) != 0
			collections = append(collections, collection)
			return nil
		},
	})

	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) {
	conn, err := dcm.pool.Take(context.Background())
	if err != nil {
		return nil, fmt.Errorf("failed to get connection: %w", err)
	}
	defer dcm.pool.Put(conn)

	query := `SELECT collection_item_id
			  FROM character_collection_items
			  WHERE char_id = ? AND collection_id = ?`

	var itemIDs []int32
	err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
		Args: []any{characterID, collectionID},
		ResultFunc: func(stmt *sqlite.Stmt) error {
			itemID := int32(stmt.ColumnInt64(0))
			itemIDs = append(itemIDs, itemID)
			return nil
		},
	})

	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 {
	conn, err := dcm.pool.Take(context.Background())
	if err != nil {
		return fmt.Errorf("failed to get connection: %w", err)
	}
	defer dcm.pool.Put(conn)

	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 = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
		Args: []any{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 {
	conn, err := dcm.pool.Take(context.Background())
	if err != nil {
		return fmt.Errorf("failed to get connection: %w", err)
	}
	defer dcm.pool.Put(conn)

	query := `INSERT OR IGNORE INTO character_collection_items (char_id, collection_id, collection_item_id)
			  VALUES (?, ?, ?)`

	err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
		Args: []any{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
	}

	conn, err := dcm.pool.Take(context.Background())
	if err != nil {
		return fmt.Errorf("failed to get connection: %w", err)
	}
	defer dcm.pool.Put(conn)

	// Use a transaction for atomic updates
	err = sqlitex.Execute(conn, "BEGIN", nil)
	if err != nil {
		return fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer sqlitex.Execute(conn, "ROLLBACK", nil)

	for _, collection := range collections {
		if !collection.GetSaveNeeded() {
			continue
		}

		// Save collection completion status
		if err := dcm.savePlayerCollectionInTx(conn, characterID, collection); err != nil {
			return fmt.Errorf("failed to save collection %d: %w", collection.GetID(), err)
		}

		// Save found items
		if err := dcm.savePlayerCollectionItemsInTx(conn, characterID, collection); err != nil {
			return fmt.Errorf("failed to save collection items for collection %d: %w", collection.GetID(), err)
		}
	}

	return sqlitex.Execute(conn, "COMMIT", nil)
}

// savePlayerCollectionInTx saves a single collection within a transaction
func (dcm *DatabaseCollectionManager) savePlayerCollectionInTx(conn *sqlite.Conn, 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 = ?`

	return sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
		Args: []any{characterID, collection.GetID(), completedInt, completedInt},
	})
}

// savePlayerCollectionItemsInTx saves collection items within a transaction
func (dcm *DatabaseCollectionManager) savePlayerCollectionItemsInTx(conn *sqlite.Conn, 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 := sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
				Args: []any{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 {
	conn, err := dcm.pool.Take(context.Background())
	if err != nil {
		return fmt.Errorf("failed to get connection: %w", err)
	}
	defer dcm.pool.Put(conn)

	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 := sqlitex.Execute(conn, query, nil)
		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 := sqlitex.Execute(conn, query, nil)
		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) {
	conn, err := dcm.pool.Take(context.Background())
	if err != nil {
		return 0, fmt.Errorf("failed to get connection: %w", err)
	}
	defer dcm.pool.Put(conn)

	query := "SELECT COUNT(*) FROM collections"

	var count int
	err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
		ResultFunc: func(stmt *sqlite.Stmt) error {
			count = int(stmt.ColumnInt64(0))
			return nil
		},
	})
	if err != nil {
		return 0, fmt.Errorf("failed to get collection count: %w", err)
	}

	return count, nil
}

// GetPlayerCollectionCount returns the number of collections a player has
func (dcm *DatabaseCollectionManager) GetPlayerCollectionCount(ctx context.Context, characterID int32) (int, error) {
	conn, err := dcm.pool.Take(context.Background())
	if err != nil {
		return 0, fmt.Errorf("failed to get connection: %w", err)
	}
	defer dcm.pool.Put(conn)

	query := "SELECT COUNT(*) FROM character_collections WHERE char_id = ?"

	var count int
	err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
		Args: []any{characterID},
		ResultFunc: func(stmt *sqlite.Stmt) error {
			count = int(stmt.ColumnInt64(0))
			return nil
		},
	})
	if err != nil {
		return 0, fmt.Errorf("failed to get player collection count for character %d: %w", characterID, err)
	}

	return count, nil
}

// GetCompletedCollectionCount returns the number of completed collections for a player
func (dcm *DatabaseCollectionManager) GetCompletedCollectionCount(ctx context.Context, characterID int32) (int, error) {
	conn, err := dcm.pool.Take(context.Background())
	if err != nil {
		return 0, fmt.Errorf("failed to get connection: %w", err)
	}
	defer dcm.pool.Put(conn)

	query := "SELECT COUNT(*) FROM character_collections WHERE char_id = ? AND completed = 1"

	var count int
	err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
		Args: []any{characterID},
		ResultFunc: func(stmt *sqlite.Stmt) error {
			count = int(stmt.ColumnInt64(0))
			return nil
		},
	})
	if err != nil {
		return 0, fmt.Errorf("failed to get completed collection count for character %d: %w", characterID, err)
	}

	return count, nil
}

// DeletePlayerCollection removes a player's collection progress
func (dcm *DatabaseCollectionManager) DeletePlayerCollection(ctx context.Context, characterID, collectionID int32) error {
	conn, err := dcm.pool.Take(context.Background())
	if err != nil {
		return fmt.Errorf("failed to get connection: %w", err)
	}
	defer dcm.pool.Put(conn)

	// Use a transaction to ensure both tables are updated atomically
	err = sqlitex.Execute(conn, "BEGIN", nil)
	if err != nil {
		return fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer sqlitex.Execute(conn, "ROLLBACK", nil)

	// Delete collection items first due to foreign key constraint
	err = sqlitex.Execute(conn, "DELETE FROM character_collection_items WHERE char_id = ? AND collection_id = ?", &sqlitex.ExecOptions{
		Args: []any{characterID, collectionID},
	})
	if err != nil {
		return fmt.Errorf("failed to delete player collection items: %w", err)
	}

	// Delete collection
	err = sqlitex.Execute(conn, "DELETE FROM character_collections WHERE char_id = ? AND collection_id = ?", &sqlitex.ExecOptions{
		Args: []any{characterID, collectionID},
	})
	if err != nil {
		return fmt.Errorf("failed to delete player collection: %w", err)
	}

	return sqlitex.Execute(conn, "COMMIT", nil)
}

// GetCollectionStatistics returns database-level collection statistics
func (dcm *DatabaseCollectionManager) GetCollectionStatistics(ctx context.Context) (CollectionStatistics, error) {
	conn, err := dcm.pool.Take(context.Background())
	if err != nil {
		return CollectionStatistics{}, fmt.Errorf("failed to get connection: %w", err)
	}
	defer dcm.pool.Put(conn)

	var stats CollectionStatistics

	// Total collections
	err = sqlitex.Execute(conn, "SELECT COUNT(*) FROM collections", &sqlitex.ExecOptions{
		ResultFunc: func(stmt *sqlite.Stmt) error {
			stats.TotalCollections = int(stmt.ColumnInt64(0))
			return nil
		},
	})
	if err != nil {
		return stats, fmt.Errorf("failed to get total collections: %w", err)
	}

	// Total collection items
	err = sqlitex.Execute(conn, "SELECT COUNT(*) FROM collection_details", &sqlitex.ExecOptions{
		ResultFunc: func(stmt *sqlite.Stmt) error {
			stats.TotalItems = int(stmt.ColumnInt64(0))
			return nil
		},
	})
	if err != nil {
		return stats, fmt.Errorf("failed to get total items: %w", err)
	}

	// Players with collections
	err = sqlitex.Execute(conn, "SELECT COUNT(DISTINCT char_id) FROM character_collections", &sqlitex.ExecOptions{
		ResultFunc: func(stmt *sqlite.Stmt) error {
			stats.PlayersWithCollections = int(stmt.ColumnInt64(0))
			return nil
		},
	})
	if err != nil {
		return stats, fmt.Errorf("failed to get players with collections: %w", err)
	}

	// Completed collections across all players
	err = sqlitex.Execute(conn, "SELECT COUNT(*) FROM character_collections WHERE completed = 1", &sqlitex.ExecOptions{
		ResultFunc: func(stmt *sqlite.Stmt) error {
			stats.CompletedCollections = int(stmt.ColumnInt64(0))
			return nil
		},
	})
	if err != nil {
		return stats, fmt.Errorf("failed to get completed collections: %w", err)
	}

	// 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`
	err = sqlitex.Execute(conn, query, &sqlitex.ExecOptions{
		ResultFunc: func(stmt *sqlite.Stmt) error {
			stats.ActiveCollections = int(stmt.ColumnInt64(0))
			return nil
		},
	})
	if err != nil {
		return stats, fmt.Errorf("failed to get active collections: %w", err)
	}

	// Found items across all players
	err = sqlitex.Execute(conn, "SELECT COUNT(*) FROM character_collection_items", &sqlitex.ExecOptions{
		ResultFunc: func(stmt *sqlite.Stmt) error {
			stats.FoundItems = int(stmt.ColumnInt64(0))
			return nil
		},
	})
	if err != nil {
		return stats, fmt.Errorf("failed to get found items: %w", err)
	}

	// Total rewards
	err = sqlitex.Execute(conn, "SELECT COUNT(*) FROM collection_rewards", &sqlitex.ExecOptions{
		ResultFunc: func(stmt *sqlite.Stmt) error {
			stats.TotalRewards = int(stmt.ColumnInt64(0))
			return nil
		},
	})
	if err != nil {
		return stats, fmt.Errorf("failed to get total rewards: %w", err)
	}

	return stats, nil
}