package services import ( "database/sql" "fmt" "log" lotteryTicketHandlers "synlotto-website/internal/handlers/lottery/tickets" thunderballrules "synlotto-website/rules" services "synlotto-website/services/draws" "synlotto-website/internal/helpers" "synlotto-website/internal/models" "synlotto-website/matcher" ) func RunTicketMatching(db *sql.DB, triggeredBy string) (models.MatchRunStats, error) { stats := models.MatchRunStats{} rows, err := db.Query(` SELECT id, game_type, draw_date, ball1, ball2, ball3, ball4, ball5, ball6, bonus1, bonus2 FROM my_tickets WHERE matched_main IS NULL `) if err != nil { return stats, err } defer rows.Close() var pending []models.Ticket for rows.Next() { var t models.Ticket var b1, b2, b3, b4, b5, b6, bo1, bo2 sql.NullInt64 if err := rows.Scan( &t.Id, &t.GameType, &t.DrawDate, &b1, &b2, &b3, &b4, &b5, &b6, &bo1, &bo2, ); err != nil { continue } t.Ball1 = int(b1.Int64) t.Ball2 = int(b2.Int64) t.Ball3 = int(b3.Int64) t.Ball4 = int(b4.Int64) t.Ball5 = int(b5.Int64) t.Ball6 = int(b6.Int64) t.Bonus1 = helpers.IntPtrIfValid(bo1) t.Bonus2 = helpers.IntPtrIfValid(bo2) pending = append(pending, t) } for _, t := range pending { matchTicket := models.MatchTicket{ ID: t.Id, GameType: t.GameType, DrawDate: t.DrawDate, Balls: helpers.BuildBallsSlice(t), BonusBalls: helpers.BuildBonusSlice(t), } draw := services.GetDrawResultForTicket(db, t.GameType, t.DrawDate) result := lotteryTicketHandlers.MatchTicketToDraw(matchTicket, draw, thunderballrules.ThunderballPrizeRules) if result.MatchedDrawID == 0 { continue } _, err := db.Exec(` UPDATE my_tickets SET matched_main = ?, matched_bonus = ?, prize_tier = ?, is_winner = ? WHERE id = ? `, result.MatchedMain, result.MatchedBonus, result.PrizeTier, result.IsWinner, t.Id) if err != nil { log.Println("⚠️ Failed to update ticket match:", err) continue } stats.TicketsMatched++ if result.IsWinner { stats.WinnersFound++ } } _, _ = db.Exec(` INSERT INTO log_ticket_matching (triggered_by, tickets_matched, winners_found) VALUES (?, ?, ?) `, triggeredBy, stats.TicketsMatched, stats.WinnersFound) return stats, nil } func UpdateMissingPrizes(db *sql.DB) error { type TicketInfo struct { ID int GameType string DrawDate string Main int Bonus int } var tickets []TicketInfo rows, err := db.Query(` SELECT id, game_type, draw_date, matched_main, matched_bonus FROM my_tickets WHERE is_winner = 1 AND (prize_label IS NULL OR prize_label = '') `) if err != nil { return err } defer rows.Close() for rows.Next() { var t TicketInfo if err := rows.Scan(&t.ID, &t.GameType, &t.DrawDate, &t.Main, &t.Bonus); err != nil { log.Println("⚠️ Failed to scan row:", err) continue } tickets = append(tickets, t) } for _, t := range tickets { if t.GameType != "Thunderball" { continue } idx, ok := thunderballrules.GetThunderballPrizeIndex(t.Main, t.Bonus) if !ok { log.Printf("❌ No index for %d main, %d bonus", t.Main, t.Bonus) continue } query := fmt.Sprintf(`SELECT prize%d_per_winner FROM prizes_thunderball WHERE draw_date = ?`, idx) var amount int err := db.QueryRow(query, t.DrawDate).Scan(&amount) if err != nil { log.Printf("❌ Prize lookup failed for ticket %d: %v", t.ID, err) continue } label := "Free Ticket" if amount > 0 { label = fmt.Sprintf("£%.2f", float64(amount)) } _, err = db.Exec(` UPDATE my_tickets SET prize_amount = ?, prize_label = ? WHERE id = ? `, float64(amount), label, t.ID) if err != nil { log.Printf("❌ Failed to update ticket %d: %v", t.ID, err) } else { log.Printf("✅ Updated ticket %d → %s", t.ID, label) } } return nil } func RefreshTicketPrizes(db *sql.DB) error { type TicketRow struct { ID int GameType string DrawDate string B1, B2, B3, B4, B5, B6 sql.NullInt64 Bonus1, Bonus2 sql.NullInt64 } var tickets []TicketRow rows, err := db.Query(` SELECT id, game_type, draw_date, ball1, ball2, ball3, ball4, ball5, ball6, bonus1, bonus2 FROM my_tickets `) if err != nil { return err } defer rows.Close() for rows.Next() { var t TicketRow if err := rows.Scan(&t.ID, &t.GameType, &t.DrawDate, &t.B1, &t.B2, &t.B3, &t.B4, &t.B5, &t.B6, &t.Bonus1, &t.Bonus2); err != nil { log.Println("⚠️ Failed to scan ticket:", err) continue } tickets = append(tickets, t) } rows.Close() for _, row := range tickets { matchTicket := models.MatchTicket{ GameType: row.GameType, DrawDate: row.DrawDate, Balls: helpers.BuildBallsFromNulls(row.B1, row.B2, row.B3, row.B4, row.B5, row.B6), BonusBalls: helpers.BuildBonusFromNulls(row.Bonus1, row.Bonus2), } draw := services.GetDrawResultForTicket(db, row.GameType, row.DrawDate) if draw.DrawID == 0 { log.Printf("❌ No draw result for %s (%s)", row.DrawDate, row.GameType) continue } mainMatches := helpers.CountMatches(matchTicket.Balls, draw.Balls) bonusMatches := helpers.CountMatches(matchTicket.BonusBalls, draw.BonusBalls) prizeTier := matcher.GetPrizeTier(row.GameType, mainMatches, bonusMatches, thunderballrules.ThunderballPrizeRules) isWinner := prizeTier != "" var label string var amount float64 if row.GameType == "Thunderball" { idx, ok := thunderballrules.GetThunderballPrizeIndex(mainMatches, bonusMatches) if ok { query := fmt.Sprintf(`SELECT prize%d_per_winner FROM prizes_thunderball WHERE draw_date = ?`, idx) var val int err := db.QueryRow(query, row.DrawDate).Scan(&val) if err == nil { amount = float64(val) if val > 0 { label = fmt.Sprintf("£%.2f", amount) } else { label = "Free Ticket" } } } } log.Printf("🧪 Ticket %d → Matches: %d+%d, Tier: %s, Winner: %v, Label: %s, Amount: %.2f", row.ID, mainMatches, bonusMatches, prizeTier, isWinner, label, amount) res, err := db.Exec(` UPDATE my_tickets SET matched_main = ?, matched_bonus = ?, prize_tier = ?, is_winner = ?, prize_amount = ?, prize_label = ? WHERE id = ? `, mainMatches, bonusMatches, prizeTier, isWinner, amount, label, row.ID) if err != nil { log.Printf("❌ Failed to update ticket %d: %v", row.ID, err) continue } rowsAffected, _ := res.RowsAffected() log.Printf("✅ Ticket %d updated — rows affected: %d | Tier: %s | Label: %s | Matches: %d+%d", row.ID, rowsAffected, prizeTier, label, mainMatches, bonusMatches) } return nil }