package handlers import ( "database/sql" "fmt" "io" "log" "net/http" "os" "strconv" "time" httpHelpers "synlotto-website/helpers/http" securityHelpers "synlotto-website/helpers/security" templateHelpers "synlotto-website/helpers/template" draws "synlotto-website/services/draws" "synlotto-website/helpers" "synlotto-website/models" "github.com/gorilla/csrf" ) func AddTicket(db *sql.DB) http.HandlerFunc { return httpHelpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { rows, err := db.Query(` SELECT DISTINCT draw_date FROM results_thunderball ORDER BY draw_date DESC `) if err != nil { log.Println("โŒ Failed to load draw dates:", err) http.Error(w, "Unable to load draw dates", http.StatusInternalServerError) return } defer rows.Close() var drawDates []string for rows.Next() { var date string if err := rows.Scan(&date); err == nil { drawDates = append(drawDates, date) } } data := models.TemplateData{} context := templateHelpers.TemplateContext(w, r, data) context["csrfField"] = csrf.TemplateField(r) context["DrawDates"] = drawDates tmpl := templateHelpers.LoadTemplateFiles("add_ticket.html", "templates/account/tickets/add_ticket.html") err = tmpl.ExecuteTemplate(w, "layout", context) if err != nil { log.Println("โŒ Template render error:", err) http.Error(w, "Error rendering form", http.StatusInternalServerError) } return } err := r.ParseMultipartForm(10 << 20) if err != nil { http.Error(w, "Invalid form", http.StatusBadRequest) log.Println("โŒ Failed to parse form:", err) return } userID, ok := securityHelpers.GetCurrentUserID(r) if !ok { http.Redirect(w, r, "/account/login", http.StatusSeeOther) return } game := r.FormValue("game_type") drawDate := r.FormValue("draw_date") purchaseMethod := r.FormValue("purchase_method") purchaseDate := r.FormValue("purchase_date") purchaseTime := r.FormValue("purchase_time") if purchaseTime != "" { purchaseDate += "T" + purchaseTime } imagePath := "" file, handler, err := r.FormFile("ticket_image") if err == nil && handler != nil { defer file.Close() filename := fmt.Sprintf("uploads/ticket_%d_%s", time.Now().UnixNano(), handler.Filename) out, err := os.Create(filename) if err == nil { defer out.Close() io.Copy(out, file) imagePath = filename } } var ballCount, bonusCount int switch game { case "Thunderball": ballCount, bonusCount = 5, 1 case "Lotto": ballCount, bonusCount = 6, 0 case "EuroMillions": ballCount, bonusCount = 5, 2 case "SetForLife": ballCount, bonusCount = 5, 1 default: http.Error(w, "Unsupported game type", http.StatusBadRequest) return } balls := make([][]int, ballCount) bonuses := make([][]int, bonusCount) for i := 1; i <= ballCount; i++ { field := fmt.Sprintf("ball%d[]", i) balls[i-1] = helpers.ParseIntSlice(r.Form[field]) log.Printf("๐Ÿ”ข %s: %v", field, balls[i-1]) } for i := 1; i <= bonusCount; i++ { field := fmt.Sprintf("bonus%d[]", i) bonuses[i-1] = helpers.ParseIntSlice(r.Form[field]) log.Printf("๐ŸŽฏ %s: %v", field, bonuses[i-1]) } lineCount := 0 if len(balls) > 0 { lineCount = len(balls[0]) } log.Println("๐Ÿงพ Total lines to insert:", lineCount) for i := 0; i < lineCount; i++ { b := make([]int, 6) bo := make([]int, 2) valid := true for j := 0; j < ballCount; j++ { if j < len(balls) && i < len(balls[j]) { b[j] = balls[j][i] if b[j] == 0 { valid = false } } } for j := 0; j < bonusCount; j++ { if j < len(bonuses) && i < len(bonuses[j]) { bo[j] = bonuses[j][i] if bo[j] == 0 { valid = false } } } if !valid { log.Printf("โš ๏ธ Skipping invalid line %d (incomplete values)", i+1) continue } _, err := db.Exec(` INSERT INTO my_tickets ( userId, game_type, draw_date, ball1, ball2, ball3, ball4, ball5, ball6, bonus1, bonus2, purchase_method, purchase_date, image_path ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, userID, game, drawDate, b[0], b[1], b[2], b[3], b[4], b[5], bo[0], bo[1], purchaseMethod, purchaseDate, imagePath, ) if err != nil { log.Println("โŒ Failed to insert ticket line:", err) } else { log.Printf("โœ… Ticket line %d saved", i+1) // ToDo create audit } } http.Redirect(w, r, "/tickets", http.StatusSeeOther) }) } func SubmitTicket(db *sql.DB) http.HandlerFunc { return httpHelpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) { err := r.ParseMultipartForm(10 << 20) if err != nil { http.Error(w, "Invalid form", http.StatusBadRequest) return } userID, ok := securityHelpers.GetCurrentUserID(r) if !ok { http.Redirect(w, r, "/account/login", http.StatusSeeOther) return } game := r.FormValue("game_type") drawDate := r.FormValue("draw_date") purchaseMethod := r.FormValue("purchase_method") purchaseDate := r.FormValue("purchase_date") purchaseTime := r.FormValue("purchase_time") if purchaseTime != "" { purchaseDate += "T" + purchaseTime } imagePath := "" file, handler, err := r.FormFile("ticket_image") if err == nil && handler != nil { defer file.Close() filename := fmt.Sprintf("uploads/ticket_%d_%s", time.Now().UnixNano(), handler.Filename) out, err := os.Create(filename) if err == nil { defer out.Close() io.Copy(out, file) imagePath = filename } } ballCount := 6 bonusCount := 2 balls := make([][]int, ballCount) bonuses := make([][]int, bonusCount) for i := 1; i <= ballCount; i++ { balls[i-1] = helpers.ParseIntSlice(r.Form["ball"+strconv.Itoa(i)]) } for i := 1; i <= bonusCount; i++ { bonuses[i-1] = helpers.ParseIntSlice(r.Form["bonus"+strconv.Itoa(i)]) } lineCount := len(balls[0]) for i := 0; i < lineCount; i++ { var b [6]int var bo [2]int for j := 0; j < ballCount; j++ { if j < len(balls) && i < len(balls[j]) { b[j] = balls[j][i] } } for j := 0; j < bonusCount; j++ { if j < len(bonuses) && i < len(bonuses[j]) { bo[j] = bonuses[j][i] } } _, err := db.Exec(` INSERT INTO my_tickets ( user_id, game_type, draw_date, ball1, ball2, ball3, ball4, ball5, ball6, bonus1, bonus2, purchase_method, purchase_date, image_path ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, userID, game, drawDate, b[0], b[1], b[2], b[3], b[4], b[5], bo[0], bo[1], purchaseMethod, purchaseDate, imagePath, ) if err != nil { log.Println("โŒ Insert failed:", err) } } http.Redirect(w, r, "/tickets", http.StatusSeeOther) }) } func GetMyTickets(db *sql.DB) http.HandlerFunc { return httpHelpers.AuthMiddleware(func(w http.ResponseWriter, r *http.Request) { data := models.TemplateData{} var tickets []models.Ticket context := templateHelpers.TemplateContext(w, r, data) context["Tickets"] = tickets userID, ok := securityHelpers.GetCurrentUserID(r) if !ok { http.Redirect(w, r, "/account/login", http.StatusSeeOther) return } rows, err := db.Query(` SELECT id, game_type, draw_date, ball1, ball2, ball3, ball4, ball5, ball6, bonus1, bonus2, purchase_method, purchase_date, image_path, duplicate, matched_main, matched_bonus, prize_tier, is_winner, prize_label, prize_amount FROM my_tickets WHERE userid = ? ORDER BY draw_date DESC, created_at DESC `, userID) if err != nil { log.Println("โŒ Query failed:", err) http.Error(w, "Could not load tickets", http.StatusInternalServerError) return } defer rows.Close() for rows.Next() { var t models.Ticket var b1, b2, b3, b4, b5, b6, bo1, bo2 sql.NullInt64 var matchedMain, matchedBonus sql.NullInt64 var prizeTier sql.NullString var isWinner sql.NullBool var prizeLabel sql.NullString var prizeAmount sql.NullFloat64 err := rows.Scan( &t.Id, &t.GameType, &t.DrawDate, &b1, &b2, &b3, &b4, &b5, &b6, &bo1, &bo2, &t.PurchaseMethod, &t.PurchaseDate, &t.ImagePath, &t.Duplicate, &matchedMain, &matchedBonus, &prizeTier, &isWinner, &prizeLabel, &prizeAmount, ) if err != nil { log.Println("โš ๏ธ Failed to scan ticket row:", err) continue } // Build primary number + bonus fields 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) if matchedMain.Valid { t.MatchedMain = int(matchedMain.Int64) } if matchedBonus.Valid { t.MatchedBonus = int(matchedBonus.Int64) } if prizeTier.Valid { t.PrizeTier = prizeTier.String } if isWinner.Valid { t.IsWinner = isWinner.Bool } if prizeLabel.Valid { t.PrizeLabel = prizeLabel.String } if prizeAmount.Valid { t.PrizeAmount = prizeAmount.Float64 } // Build balls slices (for template use) t.Balls = helpers.BuildBallsSlice(t) t.BonusBalls = helpers.BuildBonusSlice(t) // ๐ŸŽฏ Get the actual draw info (used to show which numbers matched) draw := draws.GetDrawResultForTicket(db, t.GameType, t.DrawDate) t.MatchedDraw = draw // โœ… DEBUG log.Printf("โœ… Ticket #%d", t.Id) log.Printf("Balls: %v", t.Balls) log.Printf("DrawResult: %+v", draw) tickets = append(tickets, t) } tmpl := templateHelpers.LoadTemplateFiles("my_tickets.html", "templates/account/tickets/my_tickets.html") err = tmpl.ExecuteTemplate(w, "layout", context) if err != nil { log.Println("โŒ Template error:", err) http.Error(w, "Error rendering page", http.StatusInternalServerError) } }) }