package orchestration import ( "context" "fmt" "time" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "github.com/SamuelTariku/FortuneBet-Backend/internal/config" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/repository" virtualgameservice "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/veli" "github.com/jackc/pgx/v5/pgtype" ) type Service struct { virtualGameSvc virtualgameservice.VirtualGameService veliVirtualGameSvc veli.VeliVirtualGameService repo repository.VirtualGameRepository cfg *config.Config client *veli.Client } func New(virtualGameSvc virtualgameservice.VirtualGameService, repo repository.VirtualGameRepository, cfg *config.Config, client *veli.Client) *Service { return &Service{ virtualGameSvc: virtualGameSvc, repo: repo, cfg: cfg, client: client, } } func (s *Service) AddProviders(ctx context.Context, req domain.ProviderRequest) (*domain.ProviderResponse, error) { // logger := s.mongoLogger.With(zap.String("service", "AddProviders"), zap.Any("ProviderRequest", req)) // 0. Remove all existing providers first if err := s.repo.DeleteAllVirtualGameProviders(ctx); err != nil { // logger.Error("failed to delete all virtual game providers", zap.Error(err)) return nil, fmt.Errorf("failed to clear existing providers: %w", err) } // 1. Prepare signature parameters sigParams := map[string]any{ "brandId": req.BrandID, } // Optional fields sigParams["extraData"] = fmt.Sprintf("%t", req.ExtraData) // false is still included if req.Size > 0 { sigParams["size"] = fmt.Sprintf("%d", req.Size) } else { sigParams["size"] = "" } if req.Page > 0 { sigParams["page"] = fmt.Sprintf("%d", req.Page) } else { sigParams["page"] = "" } // 2. Call external API var res domain.ProviderResponse if err := s.client.Post(ctx, "/game-lists/public/providers", req, sigParams, &res); err != nil { return nil, fmt.Errorf("failed to fetch providers: %w", err) } // 3. Loop through fetched providers and insert into DB for _, p := range res.Items { createParams := dbgen.CreateVirtualGameProviderParams{ ProviderID: p.ProviderID, ProviderName: p.ProviderName, LogoDark: pgtype.Text{String: p.LogoForDark, Valid: p.LogoForDark != ""}, LogoLight: pgtype.Text{String: p.LogoForLight, Valid: p.LogoForLight != ""}, Enabled: true, } if _, err := s.repo.CreateVirtualGameProvider(ctx, createParams); err != nil { // logger.Error("failed to add provider", zap.Error(err)) return nil, fmt.Errorf("failed to add provider %s: %w", p.ProviderID, err) } } // 4. Always add "popok" provider manually popokParams := dbgen.CreateVirtualGameProviderParams{ ProviderID: "popok", ProviderName: "Popok Gaming", LogoDark: pgtype.Text{String: fmt.Sprintf("%v/static/logos/popok-dark.png", s.cfg.PopOK.CallbackURL), Valid: true}, // adjust as needed LogoLight: pgtype.Text{String: fmt.Sprintf("%v/static/logos/popok-light.png", s.cfg.PopOK.CallbackURL), Valid: true}, // adjust as needed Enabled: true, } atlasParams := dbgen.CreateVirtualGameProviderParams{ ProviderID: "atlas", ProviderName: "Atlas Gaming", LogoDark: pgtype.Text{String: "/static/logos/atlas-dark.png", Valid: true}, // adjust as needed LogoLight: pgtype.Text{String: "/static/logos/atlas-light.png", Valid: true}, // adjust as needed Enabled: true, } if _, err := s.repo.CreateVirtualGameProvider(ctx, popokParams); err != nil { // logger.Error("failed to add popok provider", zap.Any("popokParams", popokParams), zap.Error(err)) return nil, fmt.Errorf("failed to add popok provider: %w", err) } if _, err := s.repo.CreateVirtualGameProvider(ctx, atlasParams); err != nil { return nil, fmt.Errorf("failed to add atlas provider: %w", err) } // Optionally also append it to the response for consistency // res.Items = append(res.Items, domain.VirtualGameProvider{ // ProviderID: uuid.New().String(), // ProviderName: "Popok Gaming", // LogoForDark: "/static/logos/popok-dark.png", // LogoForLight: "/static/logos/popok-light.png", // }) return &res, nil } func (s *Service) GetAllVirtualGames(ctx context.Context, params dbgen.GetAllVirtualGamesParams) ([]domain.UnifiedGame, error) { // Build params for repo call // logger := s.mongoLogger.With(zap.String("service", "GetAllVirtualGames"), zap.Any("params", params)) rows, err := s.repo.ListAllVirtualGames(ctx, params) if err != nil { // logger.Error("[GetAllVirtualGames] Failed to fetch virtual games", zap.Error(err)) return nil, fmt.Errorf("failed to fetch virtual games: %w", err) } var allGames []domain.UnifiedGame for _, r := range rows { // --- Convert nullable Rtp to *float64 --- var rtpPtr *float64 if r.Rtp.Valid { rtpFloat, err := r.Rtp.Float64Value() if err == nil { rtpPtr = new(float64) *rtpPtr = rtpFloat.Float64 } } var betsFloat64 []float64 for _, bet := range r.Bets { if bet.Valid { betFloat, err := bet.Float64Value() if err == nil { betsFloat64 = append(betsFloat64, betFloat.Float64) } } } allGames = append(allGames, domain.UnifiedGame{ GameID: r.GameID, ProviderID: r.ProviderID, Provider: r.ProviderName, Name: r.Name, Category: r.Category.String, DeviceType: r.DeviceType.String, Volatility: r.Volatility.String, RTP: rtpPtr, HasDemo: r.HasDemo.Bool, HasFreeBets: r.HasFreeBets.Bool, Bets: betsFloat64, Thumbnail: r.Thumbnail.String, Status: int(r.Status.Int32), // nullable status }) } return allGames, nil } func (s *Service) FetchAndStoreAllVirtualGames(ctx context.Context, req domain.ProviderRequest, currency string) ([]domain.UnifiedGame, error) { // logger := s.mongoLogger.With( // zap.String("service", "FetchAndStoreAllVirtualGames"), // zap.Any("ProviderRequest", req), // ) // This is necessary since the provider is a foreign key _, err := s.AddProviders(ctx, req) if err != nil { return nil, fmt.Errorf("failed to add providers to database: %w", err) } var allGames []domain.UnifiedGame // --- 1. Existing providers (Veli Games) --- providersRes, err := s.veliVirtualGameSvc.GetProviders(ctx, req) if err != nil { // logger.Error("Failed to fetch provider", zap.Error(err)) return nil, fmt.Errorf("failed to fetch providers: %w", err) } // --- 2. Fetch games for each provider (Veli Games) --- for _, p := range providersRes.Items { games, err := s.veliVirtualGameSvc.GetGames(ctx, domain.GameListRequest{ BrandID: s.cfg.VeliGames.BrandID, ProviderID: p.ProviderID, Page: req.Page, Size: req.Size, }) if err != nil { // logger.Error("failed to get veli games", zap.String("ProviderID", p.ProviderID), zap.Error(err)) continue // skip failing provider but continue others } for _, g := range games { unified := domain.UnifiedGame{ GameID: g.GameID, ProviderID: g.ProviderID, Provider: p.ProviderName, Name: g.Name, Category: g.Category, DeviceType: g.DeviceType, HasDemo: g.HasDemoMode, HasFreeBets: g.HasFreeBets, } allGames = append(allGames, unified) // Save to DB _, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{ GameID: g.GameID, ProviderID: g.ProviderID, Name: g.Name, Category: pgtype.Text{ String: g.Category, Valid: g.Category != "", }, DeviceType: pgtype.Text{ String: g.DeviceType, Valid: g.DeviceType != "", }, HasDemo: pgtype.Bool{ Bool: g.HasDemoMode, Valid: true, }, HasFreeBets: pgtype.Bool{ Bool: g.HasFreeBets, Valid: true, }, }) if err != nil { // logger.Error("failed to create virtual game", zap.Error(err)) } } } // --- 3. Fetch Atlas-V games --- atlasGames, err := s.veliVirtualGameSvc.GetAtlasVGames(ctx) if err != nil { // logger.Error("failed to fetch Atlas-V games", zap.Error(err)) } else { for _, g := range atlasGames { unified := domain.UnifiedGame{ GameID: g.GameID, ProviderID: "atlasv", Provider: "Atlas-V Gaming", // "Atlas-V" Name: g.Name, Category: g.Category, // using Type as Category Thumbnail: g.Thumbnail, HasDemo: true, DemoURL: g.DemoURL, } allGames = append(allGames, unified) // Save to DB _, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{ GameID: g.GameID, ProviderID: "atlasv", Name: g.Name, Category: pgtype.Text{ String: g.Category, Valid: g.Category != "", }, Thumbnail: pgtype.Text{ String: g.Thumbnail, Valid: g.Thumbnail != "", }, HasDemo: pgtype.Bool{ Bool: g.HasDemoMode, Valid: true, }, }) if err != nil { // logger.Error("failed to create Atlas-V virtual game", zap.Error(err)) } } } // --- 4. Handle PopOK separately --- popokGames, err := s.virtualGameSvc.ListGames(ctx, currency) if err != nil { // logger.Error("failed to fetch PopOk games", zap.Error(err)) return nil, fmt.Errorf("failed to fetch PopOK games: %w", err) } for _, g := range popokGames { unified := domain.UnifiedGame{ GameID: fmt.Sprintf("%d", g.ID), ProviderID: "popok", Provider: "Popok Gaming", Name: g.GameName, Category: "Crash", Bets: g.Bets, Thumbnail: g.Thumbnail, Status: g.Status, } allGames = append(allGames, unified) // Convert []float64 to []pgtype.Numeric var betsNumeric []pgtype.Numeric for _, bet := range g.Bets { var num pgtype.Numeric _ = num.Scan(bet) betsNumeric = append(betsNumeric, num) } // Save to DB _, err = s.repo.CreateVirtualGame(ctx, dbgen.CreateVirtualGameParams{ GameID: fmt.Sprintf("%d", g.ID), ProviderID: "popok", Name: g.GameName, Bets: betsNumeric, Thumbnail: pgtype.Text{ String: g.Thumbnail, Valid: g.Thumbnail != "", }, Status: pgtype.Int4{ Int32: int32(g.Status), Valid: true, }, HasDemo: pgtype.Bool{ Bool: true, Valid: true, }, Category: pgtype.Text{ String: "Crash", Valid: true, }, }) if err != nil { // logger.Error("failed to create PopOK virtual game", zap.Error(err)) } } return allGames, nil } func (s *Service) RemoveProvider(ctx context.Context, providerID string) error { return s.repo.DeleteVirtualGameProvider(ctx, providerID) } // Fetch provider by provider_id func (s *Service) GetProviderByID(ctx context.Context, providerID string) (dbgen.VirtualGameProvider, error) { return s.repo.GetVirtualGameProviderByID(ctx, providerID) } // List providers with pagination func (s *Service) ListProviders(ctx context.Context, limit, offset int32) ([]domain.VirtualGameProvider, int64, error) { providers, err := s.repo.ListVirtualGameProviders(ctx, limit, offset) if err != nil { return nil, 0, err } total, err := s.repo.CountVirtualGameProviders(ctx) if err != nil { return nil, 0, err } // Convert []dbgen.VirtualGameProvider to []domain.VirtualGameProvider domainProviders := make([]domain.VirtualGameProvider, len(providers)) for i, p := range providers { var logoDark *string if p.LogoDark.Valid { logoDark = &p.LogoDark.String } var logoLight *string if p.LogoLight.Valid { logoLight = &p.LogoLight.String } domainProviders[i] = domain.VirtualGameProvider{ ProviderID: p.ProviderID, ProviderName: p.ProviderName, Enabled: p.Enabled, LogoDark: logoDark, LogoLight: logoLight, CreatedAt: p.CreatedAt.Time, UpdatedAt: &p.UpdatedAt.Time, // Add other fields as needed } } return domainProviders, total, nil } // Enable/Disable a provider func (s *Service) SetProviderEnabled(ctx context.Context, providerID string, enabled bool) (*domain.VirtualGameProvider, error) { provider, err := s.repo.UpdateVirtualGameProviderEnabled(ctx, providerID, enabled) if err != nil { //s.logger.Error("Failed to update provider enabled status", "provider_id", providerID, "enabled", enabled, "error", err) return nil, err } now := time.Now() provider.UpdatedAt.Time = now domainProvider := &domain.VirtualGameProvider{ ProviderID: provider.ProviderID, ProviderName: provider.ProviderName, Enabled: provider.Enabled, CreatedAt: provider.CreatedAt.Time, UpdatedAt: &provider.UpdatedAt.Time, // Add other fields as needed } return domainProvider, nil } func (s *Service) CreateVirtualGameProviderReport(ctx context.Context, report domain.CreateVirtualGameProviderReport) (domain.VirtualGameProviderReport, error) { // Example: logger := s.mongoLogger.With(zap.String("service", "CreateVirtualGameProviderReport"), zap.Any("Report", report)) // Call repository to create the report created, err := s.repo.CreateVirtualGameProviderReport(ctx, report) if err != nil { // logger.Error("failed to create provider report", zap.Error(err), zap.Any("report", report)) return domain.VirtualGameProviderReport{}, fmt.Errorf("failed to create provider report for provider %s: %w", report.ProviderID, err) } // Return the created report return created, nil } func (s *Service) CreateVirtualGameReport(ctx context.Context, report domain.CreateVirtualGameReport) (domain.VirtualGameReport, error) { // Example: logger := s.mongoLogger.With(zap.String("service", "CreateVirtualGameReport"), zap.Any("Report", report)) // Call repository to create the report created, err := s.repo.CreateVirtualGameReport(ctx, report) if err != nil { // logger.Error("failed to create game report", zap.Error(err), zap.Any("report", report)) return domain.VirtualGameReport{}, fmt.Errorf("failed to create game report for game %s: %w", report.GameID, err) } // Return the created report return created, nil } func (s *Service) GetVirtualGameProviderReportByProviderAndDate( ctx context.Context, providerID string, reportDate time.Time, reportType string, ) (domain.VirtualGameProviderReport, error) { // Example logger if needed // logger := s.mongoLogger.With(zap.String("service", "GetVirtualGameProviderReportByProviderAndDate"), // zap.String("provider_id", providerID), // zap.Time("report_date", reportDate), // zap.String("report_type", reportType), // ) report, err := s.repo.GetVirtualGameProviderReportByProviderAndDate(ctx, providerID, reportDate, reportType) if err != nil { // logger.Error("failed to retrieve virtual game provider report", zap.Error(err)) return domain.VirtualGameProviderReport{}, fmt.Errorf( "failed to retrieve provider report for provider %s on %s (%s): %w", providerID, reportDate.Format("2006-01-02"), reportType, err, ) } return report, nil } func (s *Service) UpdateVirtualGameProviderReportByDate( ctx context.Context, providerID string, reportDate time.Time, reportType string, totalGamesPlayed int64, totalBets float64, totalPayouts float64, totalPlayers int64, ) error { // Optionally log or trace the update // Example: s.mongoLogger.Info("Updating virtual game provider report", // zap.String("provider_id", providerID), // zap.Time("report_date", reportDate), // zap.String("report_type", reportType), // ) err := s.repo.UpdateVirtualGameProviderReportByDate( ctx, providerID, reportDate, reportType, totalGamesPlayed, totalBets, totalPayouts, totalPlayers, ) if err != nil { return fmt.Errorf("failed to update provider report for provider %s on %s (%s): %w", providerID, reportDate.Format("2006-01-02"), reportType, err, ) } return nil } func (s *Service) ListVirtualGameProviderReportsByGamesPlayedAsc(ctx context.Context) ([]domain.VirtualGameProviderReport, error) { reports, err := s.repo.ListVirtualGameProviderReportsByGamesPlayedAsc(ctx) if err != nil { return nil, fmt.Errorf("failed to fetch virtual game provider reports ascending: %w", err) } return reports, nil } // ListVirtualGameProviderReportsByGamesPlayedDesc fetches all reports sorted by total_games_played descending. func (s *Service) ListVirtualGameProviderReportsByGamesPlayedDesc(ctx context.Context) ([]domain.VirtualGameProviderReport, error) { reports, err := s.repo.ListVirtualGameProviderReportsByGamesPlayedDesc(ctx) if err != nil { return nil, fmt.Errorf("failed to fetch virtual game provider reports descending: %w", err) } return reports, nil }