import { NextResponse } from "next/server"; import { prisma } from "@/lib/prisma"; import { canManageTournament } from "@/lib/permissions"; import { getSession } from "@/lib/auth-simple"; import { calculateTeamElo, calculateTeamEloChange } from "@/lib/elo-utils"; const K_FACTOR = 32; // Standard K-factor for Elo calculations export async function POST( request: Request, { params }: { params: Promise<{ id: string }> } ) { try { const { id } = await params const tournamentId = parseInt(id, 10); if (isNaN(tournamentId)) { return NextResponse.json( { error: "Invalid tournament ID" }, { status: 400 } ); } const body = await request.json(); const { games } = body; if (!games || !Array.isArray(games) || games.length === 0) { return NextResponse.json( { error: "games array is required" }, { status: 400 } ); } // Check permissions const permission = await canManageTournament(tournamentId); if (!permission.allowed) { return NextResponse.json( { error: permission.reason || 'Insufficient permissions' }, { status: 403 } ); } // Get current user const session = await getSession(); const userId = session?.user?.id || null; let importedCount = 0; let errorCount = 0; const errors: string[] = []; // Process each game for (const [index, game] of games.entries()) { try { // Find players by name (case-insensitive partial match) const [player1, player2, player3, player4] = await Promise.all([ findPlayerByName(game.player1), findPlayerByName(game.player2), findPlayerByName(game.player3), findPlayerByName(game.player4), ]); // Check if all players were found if (!player1 || !player2 || !player3 || !player4) { const missing = []; if (!player1) missing.push(game.player1); if (!player2) missing.push(game.player2); if (!player3) missing.push(game.player3); if (!player4) missing.push(game.player4); errors.push(`Game ${index + 1}: Player not found: ${missing.join(", ")}`); errorCount++; continue; } // Calculate Elo changes const team1Rating = calculateTeamElo(player1.currentElo, player2.currentElo); const team2Rating = calculateTeamElo(player3.currentElo, player4.currentElo); const team1Won = game.score1 > game.score2; const team2Won = game.score2 > game.score1; const isTie = game.score1 === game.score2; const team1ScoreActual = isTie ? 0.5 : (team1Won ? 1 : 0); const team2ScoreActual = isTie ? 0.5 : (team2Won ? 1 : 0); const team1EloChange = calculateTeamEloChange(team1Rating, team2Rating, team1ScoreActual, K_FACTOR); const team2EloChange = calculateTeamEloChange(team2Rating, team1Rating, team2ScoreActual, K_FACTOR); const player1EloChange = team1EloChange / 2; const player2EloChange = team1EloChange / 2; const player3EloChange = team2EloChange / 2; const player4EloChange = team2EloChange / 2; // Create match await prisma.match.create({ data: { eventId: tournamentId, team1P1Id: player1.id, team1P2Id: player2.id, team2P1Id: player3.id, team2P2Id: player4.id, team1Score: game.score1, team2Score: game.score2, status: "completed", playedAt: new Date(), createdById: userId, }, }); // Update player stats await updatePlayerStats(player1.id, team1Won, player1EloChange); await updatePlayerStats(player2.id, team1Won, player2EloChange); await updatePlayerStats(player3.id, team2Won, player3EloChange); await updatePlayerStats(player4.id, team2Won, player4EloChange); // Update partnership stats await updatePartnershipStats(player1.id, player2.id, team1Won, player1EloChange); await updatePartnershipStats(player3.id, player4.id, team2Won, player3EloChange); // Add players as participants if not already added for (const player of [player1, player2, player3, player4]) { try { // Check if participant already exists const existing = await prisma.eventParticipant.findFirst({ where: { eventId: tournamentId, playerId: player.id, }, }); if (!existing) { await prisma.eventParticipant.create({ data: { eventId: tournamentId, playerId: player.id, status: "participated", registrationDate: new Date(), }, }); } } catch { // Participant already exists, which is fine console.log(`Participant ${player.id} already exists in tournament ${tournamentId}`); } } importedCount++; } catch (err: unknown) { const message = err instanceof Error ? err.message : "Unknown error"; errors.push(`Game ${index + 1}: ${message}`); errorCount++; } } return NextResponse.json({ importedCount, errorCount, errors: errors.length > 0 ? errors : undefined, }); } catch (error: unknown) { console.error("Bulk game submission error:", error); const message = error instanceof Error ? error.message : "Failed to submit games"; return NextResponse.json( { error: message }, { status: 500 } ); } } async function findPlayerByName(name: string) { // Try to find existing player with exact name match first (case-sensitive) let player = await prisma.player.findFirst({ where: { name: name, }, }); if (!player) { // Try partial match (case-sensitive) player = await prisma.player.findFirst({ where: { name: { contains: name, }, }, orderBy: { name: "asc" }, }); } return player; } async function updatePlayerStats(playerId: number, won: boolean, eloChange: number) { const player = await prisma.player.findUnique({ where: { id: playerId }, }); if (!player) { throw new Error(`Player ${playerId} not found`); } await prisma.player.update({ where: { id: playerId }, data: { currentElo: player.currentElo + Math.round(eloChange), gamesPlayed: player.gamesPlayed + 1, wins: won ? player.wins + 1 : player.wins, losses: won ? player.losses : player.losses + 1, }, }); } async function updatePartnershipStats(player1Id: number, player2Id: number, won: boolean, eloChange: number) { const [smallId, largeId] = player1Id < player2Id ? [player1Id, player2Id] : [player2Id, player1Id]; const existing = await prisma.partnershipStat.findFirst({ where: { player1Id: smallId, player2Id: largeId, }, }); if (existing) { await prisma.partnershipStat.update({ where: { id: existing.id }, data: { gamesPlayed: { increment: 1 }, wins: won ? { increment: 1 } : undefined, losses: won ? undefined : { increment: 1 }, totalEloChange: { increment: eloChange }, lastPlayed: new Date(), }, }); } else { await prisma.partnershipStat.create({ data: { player1Id: smallId, player2Id: largeId, gamesPlayed: 1, wins: won ? 1 : 0, losses: won ? 0 : 1, totalEloChange: eloChange, lastPlayed: new Date(), }, }); } }