package event import ( "context" "encoding/json" "fmt" "io" "log" "net/http" "slices" "strconv" "sync" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" // "github.com/SamuelTariku/FortuneBet-Backend/internal/services/event" ) type service struct { token string store *repository.Store } func New(token string, store *repository.Store) Service { return &service{ token: token, store: store, } } func (s *service) FetchLiveEvents(ctx context.Context) error { sportIDs := []int{1, 13, 78, 18, 91, 16, 17, 14, 12, 3, 2, 4, 83, 15, 92, 94, 8, 19, 36, 66, 9, 75, 90, 95, 110, 107, 151, 162, 148} var wg sync.WaitGroup for _, sportID := range sportIDs { wg.Add(1) go func(sportID int) { defer wg.Done() url := fmt.Sprintf("https://api.b365api.com/v1/bet365/inplay?sport_id=%d&token=%s", sportID, s.token) resp, err := http.Get(url) if err != nil { fmt.Printf(" Failed request for sport_id=%d: %v\n", sportID, err) return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var data struct { Success int `json:"success"` Results [][]map[string]interface{} `json:"results"` } if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 { fmt.Printf(" Decode failed for sport_id=%d\nRaw: %s\n", sportID, string(body)) return } for _, group := range data.Results { for _, ev := range group { if getString(ev["type"]) != "EV" { continue } event := domain.Event{ ID: getString(ev["ID"]), SportID: fmt.Sprintf("%d", sportID), MatchName: getString(ev["NA"]), Score: getString(ev["SS"]), MatchMinute: getInt(ev["TM"]), TimerStatus: getString(ev["TT"]), HomeTeamID: getString(ev["HT"]), AwayTeamID: getString(ev["AT"]), HomeKitImage: getString(ev["K1"]), AwayKitImage: getString(ev["K2"]), LeagueName: getString(ev["CT"]), LeagueID: getString(ev["C2"]), LeagueCC: getString(ev["CB"]), StartTime: time.Now().UTC().Format(time.RFC3339), IsLive: true, Status: "live", MatchPeriod: getInt(ev["MD"]), AddedTime: getInt(ev["TA"]), } if err := s.store.SaveEvent(ctx, event); err != nil { fmt.Printf("Could not store live event [id=%s]: %v\n", event.ID, err) } } } }(sportID) } wg.Wait() fmt.Println("All live events fetched and stored.") return nil } func (s *service) FetchUpcomingEvents(ctx context.Context) error { sportIDs := []int{1, 18, 17} for _, sportID := range sportIDs { var totalPages int = 1 var page int = 0 var limit int = 150 var count int = 0 for page <= totalPages { page = page + 1 url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", sportID, s.token, page) log.Printf("📡 Fetching data for sport %d event data page %d/%d", sportID, page, min(limit, totalPages)) resp, err := http.Get(url) if err != nil { log.Printf("❌ Failed to fetch event data for page %d: %v", page, err) continue } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var data struct { Success int `json:"success"` Pager struct { Page int `json:"page"` PerPage int `json:"per_page"` Total int `json:"total"` } Results []struct { ID string `json:"id"` SportID string `json:"sport_id"` Time string `json:"time"` League struct { ID string `json:"id"` Name string `json:"name"` } `json:"league"` Home struct { ID string `json:"id"` Name string `json:"name"` } `json:"home"` Away *struct { ID string `json:"id"` Name string `json:"name"` } `json:"away"` } `json:"results"` } if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 { log.Printf("❌ Failed to parse json data") continue } var skippedLeague []string for _, ev := range data.Results { startUnix, _ := strconv.ParseInt(ev.Time, 10, 64) // eventID, err := strconv.ParseInt(ev.ID, 10, 64) // if err != nil { // log.Panicf("❌ Invalid event id, eventID %v", ev.ID) // continue // } leagueID, err := strconv.ParseInt(ev.League.ID, 10, 64) if err != nil { log.Printf("❌ Invalid league id, leagueID %v", ev.League.ID) continue } if !slices.Contains(domain.SupportedLeagues, leagueID) { // fmt.Printf("⚠️ Skipping league %s (%d) as it is not supported\n", ev.League.Name, leagueID) skippedLeague = append(skippedLeague, ev.League.Name) continue } event := domain.UpcomingEvent{ ID: ev.ID, SportID: ev.SportID, MatchName: "", HomeTeam: ev.Home.Name, AwayTeam: "", // handle nil safely HomeTeamID: ev.Home.ID, AwayTeamID: "", HomeKitImage: "", AwayKitImage: "", LeagueID: ev.League.ID, LeagueName: ev.League.Name, LeagueCC: "", StartTime: time.Unix(startUnix, 0).UTC(), } if ev.Away != nil { event.AwayTeam = ev.Away.Name event.AwayTeamID = ev.Away.ID event.MatchName = ev.Home.Name + " vs " + ev.Away.Name } err = s.store.SaveUpcomingEvent(ctx, event) if err != nil { log.Printf("❌ Failed to save upcoming event %s", event.ID) } } log.Printf("⚠️ Skipped leagues %v", len(skippedLeague)) // log.Printf("⚠️ Total pages %v", data.Pager.Total) totalPages = data.Pager.Total / data.Pager.PerPage if count >= limit { break } if page > totalPages { break } count++ } } return nil } func getString(v interface{}) string { if str, ok := v.(string); ok { return str } return "" } func getInt(v interface{}) int { if f, ok := v.(float64); ok { return int(f) } return 0 } func (s *service) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) { return s.store.GetAllUpcomingEvents(ctx) } func (s *service) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.UpcomingEvent, error) { return s.store.GetExpiredUpcomingEvents(ctx) } func (s *service) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.ValidInt64, offset domain.ValidInt64, leagueID domain.ValidString, sportID domain.ValidString, firstStartTime domain.ValidTime, lastStartTime domain.ValidTime) ([]domain.UpcomingEvent, int64, error) { return s.store.GetPaginatedUpcomingEvents(ctx, limit, offset, leagueID, sportID, firstStartTime, lastStartTime) } func (s *service) GetUpcomingEventByID(ctx context.Context, ID string) (domain.UpcomingEvent, error) { return s.store.GetUpcomingEventByID(ctx, ID) } // func (s *service) GetAndStoreMatchResult(ctx context.Context, eventID string) error { // url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%s", s.token, eventID) // resp, err := http.Get(url) // if err != nil { // return fmt.Errorf("failed to fetch result: %w", err) // } // defer resp.Body.Close() // body, _ := io.ReadAll(resp.Body) // // Parse the API response // var apiResp struct { // Results []struct { // ID string `json:"id"` // Ss string `json:"ss"` // Full-time score // Status string `json:"time_status"` // } `json:"results"` // } // err = json.Unmarshal(body, &apiResp) // if err != nil || len(apiResp.Results) == 0 { // return fmt.Errorf("invalid response or no results found") // } // result := apiResp.Results[0] // err = s.store.UpdateFinalScore(ctx, result.ID, result.Ss, result.Status) // if err != nil { // return fmt.Errorf("failed to update final score in database: %w", err) // } // return nil // }