const { db } = require('../db'); const { parseDate } = require('../services/rating-calculator'); function getPlayerFromDB(pdgaNumber) { return new Promise((resolve, reject) => { db.get( 'SELECT * FROM players WHERE pdga_number = ?', [pdgaNumber], (err, row) => { if (err) reject(err); else resolve(row); } ); }); } function savePlayerToDB(playerData) { return new Promise((resolve, reject) => { db.run( `INSERT OR REPLACE INTO players (pdga_number, name, current_rating, rating_change, last_updated) VALUES (?, ?, ?, ?, datetime('now'))`, [playerData.pdgaNumber, playerData.name, playerData.rating, playerData.ratingChange], function(err) { if (err) reject(err); else resolve(this.lastID); } ); }); } function getRatingHistoryFromDB(pdgaNumber) { return new Promise((resolve, reject) => { db.get('SELECT id FROM players WHERE pdga_number = ?', [pdgaNumber], (err, player) => { if (err) return reject(err); if (!player) return resolve(null); db.all( 'SELECT * FROM rating_history WHERE player_id = ? ORDER BY date ASC', [player.id], (err, rows) => { if (err) reject(err); else resolve(rows); } ); }); }); } function saveRatingHistoryToDB(pdgaNumber, ratingHistory) { return new Promise((resolve, reject) => { db.get('SELECT id FROM players WHERE pdga_number = ?', [pdgaNumber], (err, player) => { if (err) return reject(err); if (!player) return reject(new Error('Player not found')); db.run('DELETE FROM rating_history WHERE player_id = ?', [player.id], (err) => { if (err) return reject(err); if (ratingHistory.length === 0) { return resolve(); } let completed = 0; const total = ratingHistory.length; ratingHistory.forEach(entry => { const parsedDate = parseDate(entry.date); db.run( 'INSERT INTO rating_history (player_id, date, rating) VALUES (?, ?, ?)', [player.id, parsedDate.toISOString().split('T')[0], entry.rating], (err) => { if (err) return reject(err); completed++; if (completed === total) { resolve(); } } ); }); }); }); }); } function getRoundHistoryFromDB(pdgaNumber) { return new Promise((resolve, reject) => { db.get('SELECT id FROM players WHERE pdga_number = ?', [pdgaNumber], (err, player) => { if (err) return reject(err); if (!player) return resolve([]); db.all( 'SELECT * FROM round_history WHERE player_id = ? ORDER BY date DESC', [player.id], (err, rows) => { if (err) reject(err); else resolve(rows); } ); }); }); } function getLastRoundUpdateDate(pdgaNumber) { return new Promise((resolve, reject) => { db.get( 'SELECT last_round_update FROM players WHERE pdga_number = ?', [pdgaNumber], (err, row) => { if (err) reject(err); else resolve(row ? row.last_round_update : null); } ); }); } function updateLastRoundUpdateDate(pdgaNumber) { return new Promise((resolve, reject) => { db.run( 'UPDATE players SET last_round_update = CURRENT_TIMESTAMP WHERE pdga_number = ?', [pdgaNumber], function(err) { if (err) reject(err); else resolve(); } ); }); } function saveRoundHistoryToDB(pdgaNumber, roundData, isIncremental = false) { return new Promise((resolve, reject) => { db.get('SELECT id FROM players WHERE pdga_number = ?', [pdgaNumber], (err, player) => { if (err) return reject(err); if (!player) return reject(new Error('Player not found')); const processRounds = () => { if (roundData.length === 0) { db.run('UPDATE players SET last_round_update = datetime("now") WHERE pdga_number = ?', [pdgaNumber], (err) => { if (err) reject(err); else resolve(); }); return; } const stmt = db.prepare('INSERT OR REPLACE INTO round_history (player_id, date, competition_name, rating) VALUES (?, ?, ?, ?)'); for (const round of roundData) { stmt.run([player.id, round.date.toISOString().split('T')[0], round.competition || 'Unknown', round.rating]); } stmt.finalize((err) => { if (err) { reject(err); } else { db.run('UPDATE players SET last_round_update = datetime("now") WHERE pdga_number = ?', [pdgaNumber], (updateErr) => { if (updateErr) reject(updateErr); else resolve(); }); } }); }; if (!isIncremental) { db.run('DELETE FROM round_history WHERE player_id = ?', [player.id], (err) => { if (err) return reject(err); processRounds(); }); } else { processRounds(); } }); }); } function savePredictedRatingToDB(pdgaNumber, predictedRating, stdDev = null) { return new Promise((resolve, reject) => { db.run( 'UPDATE players SET predicted_rating = ?, std_dev = ? WHERE pdga_number = ?', [predictedRating, stdDev, pdgaNumber], function(err) { if (err) reject(err); else resolve(); } ); }); } /** * Returns monthly rating snapshots for one player (latest entry per calendar month), * ordered oldest → newest. At most `months` entries; [] if none. */ function getMonthlyHistory(pdgaNumber, months = 12) { return new Promise((resolve, reject) => { db.get('SELECT id FROM players WHERE pdga_number = ?', [pdgaNumber], (err, player) => { if (err) return reject(err); if (!player) return resolve([]); db.all( `SELECT rating FROM rating_history WHERE player_id = ? AND date IN ( SELECT MAX(date) FROM rating_history WHERE player_id = ? GROUP BY strftime('%Y-%m', date) ) ORDER BY date DESC LIMIT ?`, [player.id, player.id, months], (err, rows) => { if (err) return reject(err); resolve(rows.map(r => r.rating).reverse()); } ); }); }); } /** * Fetches the last `months` monthly rating snapshots for ALL players in one query. * Returns a Map (oldest → newest per player). * Use this in bulk-fetch paths to avoid N+1 queries. */ function getAllMonthlyHistoriesFromDB(months = 12) { return new Promise((resolve, reject) => { db.all( `SELECT p.pdga_number, rh.date, rh.rating FROM rating_history rh JOIN players p ON rh.player_id = p.id INNER JOIN ( SELECT player_id, MAX(date) AS max_date FROM rating_history GROUP BY player_id, strftime('%Y-%m', date) ) latest ON rh.player_id = latest.player_id AND rh.date = latest.max_date ORDER BY p.pdga_number, rh.date ASC`, [], (err, rows) => { if (err) return reject(err); const map = new Map(); for (const row of rows) { if (!map.has(row.pdga_number)) map.set(row.pdga_number, []); map.get(row.pdga_number).push(row.rating); } // Trim each player's history to the requested window for (const [key, arr] of map) { if (arr.length > months) map.set(key, arr.slice(-months)); } resolve(map); } ); }); } function getLastRefresh() { return new Promise((resolve, reject) => { db.get( 'SELECT MAX(last_updated) AS lastRefresh FROM players', [], (err, row) => { if (err) reject(err); else resolve(row ? row.lastRefresh : null); } ); }); } module.exports = { getPlayerFromDB, savePlayerToDB, getRatingHistoryFromDB, saveRatingHistoryToDB, getRoundHistoryFromDB, getLastRoundUpdateDate, updateLastRoundUpdateDate, saveRoundHistoryToDB, savePredictedRatingToDB, getLastRefresh, getMonthlyHistory, getAllMonthlyHistoriesFromDB };