package odds import ( "context" "encoding/json" "fmt" "io" "net/http" "strconv" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" ) type ServiceImpl struct { token string store *repository.Store } func New(token string, store *repository.Store) *ServiceImpl { return &ServiceImpl{token: token, store: store} } func (s *ServiceImpl) FetchNonLiveOdds(ctx context.Context) error { sportIDs := []int{1, 13, 78, 18, 91, 16, 17} for _, sportID := range sportIDs { upcomingURL := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s", sportID, s.token) resp, err := http.Get(upcomingURL) if err != nil { fmt.Printf("❌ Failed to fetch upcoming for sport_id=%d: %v\n", sportID, err) continue } 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("❌ Failed to decode upcoming for sport_id=%d\nRaw: %s\n", sportID, string(body)) continue } for _, ev := range data.Results { if getString(ev["type"]) != "EV" { continue } eventID := getString(ev["ID"]) if eventID == "" { continue } prematchURL := fmt.Sprintf("https://api.b365api.com/v3/bet365/prematch?token=%s&FI=%s", s.token, eventID) oddsResp, err := http.Get(prematchURL) if err != nil { fmt.Printf("❌ Odds fetch failed for event_id=%s: %v\n", eventID, err) continue } defer oddsResp.Body.Close() oddsBody, _ := io.ReadAll(oddsResp.Body) var oddsData struct { Success int `json:"success"` Results []struct { EventID string `json:"event_id"` FI string `json:"FI"` AsianLines OddsSection `json:"asian_lines"` Goals OddsSection `json:"goals"` } `json:"results"` } if err := json.Unmarshal(oddsBody, &oddsData); err != nil || oddsData.Success != 1 || len(oddsData.Results) == 0 { fmt.Printf("❌ Failed odds decode for event_id=%s\nRaw: %s\n", eventID, string(oddsBody)) continue } result := oddsData.Results[0] finalID := result.EventID if finalID == "" { finalID = result.FI } if finalID == "" { fmt.Println("⚠️ Skipping event with missing final ID.") continue } saveOdds := func(sectionName string, section OddsSection) error { if len(section.Sp) == 0 { fmt.Printf("⚠️ No odds in section '%s' for event_id=%s\n", sectionName, finalID) return nil } updatedAtUnix, _ := strconv.ParseInt(section.UpdatedAt, 10, 64) updatedAt := time.Unix(updatedAtUnix, 0) for marketType, market := range section.Sp { if len(market.Odds) == 0 { fmt.Printf("⚠️ No odds for marketType=%s in section=%s\n", marketType, sectionName) continue } marketRecord := domain.Market{ EventID: finalID, FI: result.FI, MarketCategory: sectionName, MarketType: marketType, MarketName: market.Name, MarketID: market.ID, UpdatedAt: updatedAt, Odds: market.Odds, } s.store.SaveNonLiveOdd(ctx, marketRecord) fmt.Printf("✅ STORED MARKET: event_id=%s | type=%s | name=%s\n", finalID, marketType, market.Name) } return nil } if err := saveOdds("asian_lines", result.AsianLines); err != nil { fmt.Printf("⚠️ Skipping event %s due to asian_lines error: %v\n", finalID, err) continue } if err := saveOdds("goals", result.Goals); err != nil { fmt.Printf("⚠️ Skipping event %s due to goals error: %v\n", finalID, err) continue } fmt.Printf("✅ Done storing all odds for event_id=%s\n", finalID) } } fmt.Println("✅ All non-live odds fetched and stored.") return nil } // Odds structures type OddsMarket struct { ID string `json:"id"` Name string `json:"name"` Odds []json.RawMessage `json:"odds"` } type OddsSection struct { UpdatedAt string `json:"updated_at"` Sp map[string]OddsMarket `json:"sp"` } // Helper func getString(v interface{}) string { if str, ok := v.(string); ok { return str } return "" }