package repository import ( "context" "fmt" "time" dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/ports" "github.com/jackc/pgx/v5/pgtype" ) // NewVirtualGameReportStore returns a new VirtualGameReportStore interface func NewVirtualGameReportStore(s *Store) ports.VirtualGameReportStore { return s } // ------------------- Financial Reports ------------------- func (s *Store) CreateFinancialReport(ctx context.Context, report domain.FinancialReport) (domain.FinancialReport, error) { // pgtype.Numeric no longer exposes a Set method in this pgx version; // use zero-value pgtype.Numeric here (or replace with a proper conversion helper). totalBets := pgtype.Numeric{} totalWins := pgtype.Numeric{} // parse report.ReportDate (try RFC3339 then YYYY-MM-DD) t, err := time.Parse(time.RFC3339, report.ReportDate) if err != nil { t, err = time.Parse("2006-01-02", report.ReportDate) if err != nil { return domain.FinancialReport{}, fmt.Errorf("invalid report date: %w", err) } } dbReport, err := s.queries.CreateFinancialReport(ctx, dbgen.CreateFinancialReportParams{ GameID: report.GameID, ProviderID: report.ProviderID, ReportDate: pgtype.Date{Time: t}, ReportType: report.ReportType, TotalBets: totalBets, TotalWins: totalWins, }) if err != nil { return domain.FinancialReport{}, fmt.Errorf("failed to create financial report: %w", err) } return domain.ConvertDBFinancialReport(dbReport), nil } func (s *Store) UpsertFinancialReport(ctx context.Context, report domain.FinancialReport) (domain.FinancialReport, error) { totalBets := pgtype.Numeric{} totalWins := pgtype.Numeric{} // parse report.ReportDate t, err := time.Parse(time.RFC3339, report.ReportDate) if err != nil { t, err = time.Parse("2006-01-02", report.ReportDate) if err != nil { return domain.FinancialReport{}, fmt.Errorf("invalid report date: %w", err) } } dbReport, err := s.queries.UpsertFinancialReport(ctx, dbgen.UpsertFinancialReportParams{ GameID: report.GameID, ProviderID: report.ProviderID, ReportDate: pgtype.Date{Time: t}, ReportType: report.ReportType, TotalBets: totalBets, TotalWins: totalWins, }) if err != nil { return domain.FinancialReport{}, fmt.Errorf("failed to upsert financial report: %w", err) } return domain.ConvertDBFinancialReport(dbReport), nil } // GetFinancialReportByID fetches a single report by its ID func (s *Store) GetFinancialReportByID(ctx context.Context, id int64) (domain.FinancialReport, error) { dbReport, err := s.queries.GetFinancialReportByID(ctx, id) if err != nil { return domain.FinancialReport{}, fmt.Errorf("failed to get financial report by ID: %w", err) } return domain.ConvertDBFinancialReport(dbReport), nil } // GetFinancialReportsForGame fetches all reports for a specific game + provider in a date range func (s *Store) GetFinancialReportsForGame(ctx context.Context, gameID, providerID string, from, to string) ([]domain.FinancialReport, error) { fromDate, err := time.Parse("2006-01-02", from) if err != nil { return nil, fmt.Errorf("invalid from date: %w", err) } toDate, err := time.Parse("2006-01-02", to) if err != nil { return nil, fmt.Errorf("invalid to date: %w", err) } dbReports, err := s.queries.GetFinancialReportsForGame(ctx, dbgen.GetFinancialReportsForGameParams{ GameID: gameID, ProviderID: providerID, ReportDate: pgtype.Date{Time: fromDate}, ReportDate_2: pgtype.Date{Time: toDate}, }) if err != nil { return nil, fmt.Errorf("failed to get financial reports for game: %w", err) } var result []domain.FinancialReport for _, r := range dbReports { result = append(result, domain.ConvertDBFinancialReport(r)) } return result, nil } func (s *Store) GetDailyFinancialReports(ctx context.Context, reportDate string) ([]domain.FinancialReport, error) { t, err := time.Parse("2006-01-02", reportDate) if err != nil { return nil, fmt.Errorf("invalid report date: %w", err) } dbReports, err := s.queries.GetDailyFinancialReports(ctx, pgtype.Date{Time: t}) if err != nil { return nil, fmt.Errorf("failed to get daily financial reports: %w", err) } var result []domain.FinancialReport for _, r := range dbReports { result = append(result, domain.ConvertDBFinancialReport(r)) } return result, nil } // DeleteFinancialReport deletes a financial report by ID func (s *Store) DeleteFinancialReport(ctx context.Context, id int64) error { err := s.queries.DeleteFinancialReport(ctx, id) if err != nil { return fmt.Errorf("failed to delete financial report: %w", err) } return nil } // CreateCompanyReport inserts a new company-level financial report func (s *Store) CreateCompanyReport(ctx context.Context, report domain.CompanyReport) (domain.CompanyReport, error) { // parse report date t, err := time.Parse("2006-01-02", report.ReportDate) if err != nil { return domain.CompanyReport{}, fmt.Errorf("invalid report date: %w", err) } dbReport, err := s.queries.CreateCompanyReport(ctx, dbgen.CreateCompanyReportParams{ CompanyID: report.CompanyID, ProviderID: report.ProviderID, ReportDate: pgtype.Date{Time: t}, ReportType: report.ReportType, TotalBetAmount: pgtype.Numeric{}, // zero value; set actual value if required TotalWinAmount: pgtype.Numeric{}, }) if err != nil { return domain.CompanyReport{}, fmt.Errorf("failed to create company report: %w", err) } return domain.ConvertDBCompanyReport(dbReport), nil } func (s *Store) UpsertCompanyReport(ctx context.Context, report domain.CompanyReport) (domain.CompanyReport, error) { // Convert report date t, err := time.Parse("2006-01-02", report.ReportDate) if err != nil { t, err = time.Parse(time.RFC3339, report.ReportDate) if err != nil { return domain.CompanyReport{}, fmt.Errorf("invalid report date: %w", err) } } dbReport, err := s.queries.UpsertCompanyReport(ctx, dbgen.UpsertCompanyReportParams{ CompanyID: report.CompanyID, ProviderID: report.ProviderID, ReportDate: pgtype.Date{Time: t}, ReportType: report.ReportType, TotalBetAmount: func() pgtype.Numeric { var n pgtype.Numeric _ = n.Scan(report.TotalBetAmount) return n }(), TotalWinAmount: func() pgtype.Numeric { var n pgtype.Numeric _ = n.Scan(report.TotalWinAmount) return n }(), }) if err != nil { return domain.CompanyReport{}, fmt.Errorf("failed to upsert company report: %w", err) } return domain.ConvertDBCompanyReport(dbReport), nil } func (s *Store) GetCompanyReportByID(ctx context.Context, id int64) (domain.CompanyReport, error) { dbReport, err := s.queries.GetCompanyReportByID(ctx, id) if err != nil { return domain.CompanyReport{}, fmt.Errorf("failed to get company report by ID: %w", err) } return domain.ConvertDBCompanyReport(dbReport), nil } func (s *Store) GetCompanyReportsInRange(ctx context.Context, companyID int64, providerID string, startDate, endDate string) ([]domain.CompanyReport, error) { start, err := time.Parse("2006-01-02", startDate) if err != nil { start, err = time.Parse(time.RFC3339, startDate) if err != nil { return nil, fmt.Errorf("invalid start date: %w", err) } } end, err := time.Parse("2006-01-02", endDate) if err != nil { end, err = time.Parse(time.RFC3339, endDate) if err != nil { return nil, fmt.Errorf("invalid end date: %w", err) } } dbReports, err := s.queries.GetCompanyReportsInRange(ctx, dbgen.GetCompanyReportsInRangeParams{ CompanyID: companyID, ProviderID: providerID, ReportDate: pgtype.Date{Time: start}, ReportDate_2: pgtype.Date{Time: end}, }) if err != nil { return nil, fmt.Errorf("failed to get company reports in range: %w", err) } reports := make([]domain.CompanyReport, 0, len(dbReports)) for _, r := range dbReports { reports = append(reports, domain.ConvertDBCompanyReport(r)) } return reports, nil } func (s *Store) GetCompanyProfitTrend(ctx context.Context, companyID int64, providerID string, startDate, endDate string) ([]domain.CompanyProfitTrend, error) { start, err := time.Parse("2006-01-02", startDate) if err != nil { start, err = time.Parse(time.RFC3339, startDate) if err != nil { return nil, fmt.Errorf("invalid start date: %w", err) } } end, err := time.Parse("2006-01-02", endDate) if err != nil { end, err = time.Parse(time.RFC3339, endDate) if err != nil { return nil, fmt.Errorf("invalid end date: %w", err) } } rows, err := s.queries.GetCompanyProfitTrend(ctx, dbgen.GetCompanyProfitTrendParams{ CompanyID: companyID, ProviderID: providerID, ReportDate: pgtype.Date{Time: start}, ReportDate_2: pgtype.Date{Time: end}, }) if err != nil { return nil, fmt.Errorf("failed to get company profit trend: %w", err) } trends := make([]domain.CompanyProfitTrend, 0, len(rows)) for _, r := range rows { trends = append(trends, domain.CompanyProfitTrend{ ReportDate: r.ReportDate.Time.Format("2006-01-02"), TotalProfit: float64(r.TotalProfit), }) } return trends, nil } // CreatePlayerActivityReport inserts a new player activity report func (s *Store) CreatePlayerActivityReport(ctx context.Context, report domain.PlayerActivityReport) (domain.PlayerActivityReport, error) { t, err := time.Parse("2006-01-02", report.ReportDate) if err != nil { t, err = time.Parse(time.RFC3339, report.ReportDate) if err != nil { return domain.PlayerActivityReport{}, fmt.Errorf("invalid report date: %w", err) } } createParams := dbgen.CreatePlayerActivityReportParams{ UserID: report.UserID, ReportDate: pgtype.Date{Time: t}, ReportType: report.ReportType, TotalDeposits: func() pgtype.Numeric { var n pgtype.Numeric _ = n.Scan(report.TotalDeposits) return n }(), TotalWithdrawals: func() pgtype.Numeric { var n pgtype.Numeric _ = n.Scan(report.TotalWithdrawals) return n }(), TotalBetAmount: func() pgtype.Numeric { var n pgtype.Numeric _ = n.Scan(report.TotalBetAmount) return n }(), TotalWinAmount: func() pgtype.Numeric { var n pgtype.Numeric _ = n.Scan(report.TotalWinAmount) return n }(), RoundsPlayed: pgtype.Int8{Int64: int64(report.RoundsPlayed)}, } var dbReport dbgen.VirtualGamePlayerActivityReport dbReport, err = s.queries.CreatePlayerActivityReport(ctx, createParams) if err != nil { // try upsert as a fallback upsertParams := dbgen.UpsertPlayerActivityReportParams{ UserID: report.UserID, ReportDate: pgtype.Date{Time: t}, ReportType: report.ReportType, TotalDeposits: pgtype.Numeric{Exp: int32(report.TotalDeposits)}, TotalWithdrawals: pgtype.Numeric{Exp: int32(report.TotalWithdrawals)}, TotalBetAmount: pgtype.Numeric{Exp: int32(report.TotalBetAmount)}, TotalWinAmount: pgtype.Numeric{Exp: int32(report.TotalWinAmount)}, RoundsPlayed: pgtype.Int8{Int64: int64(report.RoundsPlayed)}, } dbReport, err = s.queries.UpsertPlayerActivityReport(ctx, upsertParams) if err != nil { return domain.PlayerActivityReport{}, fmt.Errorf("failed to create or upsert player activity report: %w", err) } } return domain.ConvertDBPlayerActivityReport(dbReport), nil } func (s *Store) GetPlayerActivityByID(ctx context.Context, id int64) (domain.PlayerActivityReport, error) { dbReport, err := s.queries.GetPlayerActivityByID(ctx, id) if err != nil { return domain.PlayerActivityReport{}, err } return domain.ConvertDBPlayerActivityReport(dbReport), nil } // GetPlayerActivityByDate fetches a player activity report for a specific user and date func (s *Store) GetPlayerActivityByDate(ctx context.Context, userID int64, reportDate, reportType string) (domain.PlayerActivityReport, error) { t, err := time.Parse("2006-01-02", reportDate) if err != nil { t, err = time.Parse(time.RFC3339, reportDate) if err != nil { return domain.PlayerActivityReport{}, fmt.Errorf("invalid report date: %w", err) } } dbReport, err := s.queries.GetPlayerActivityByDate(ctx, dbgen.GetPlayerActivityByDateParams{ UserID: userID, ReportDate: pgtype.Date{Time: t}, ReportType: reportType, }) if err != nil { return domain.PlayerActivityReport{}, err } return domain.ConvertDBPlayerActivityReport(dbReport), nil } // GetPlayerActivityRange fetches all activity reports for a user in a date range func (s *Store) GetPlayerActivityRange(ctx context.Context, userID int64, startDate, endDate string) ([]domain.PlayerActivityReport, error) { start, err := time.Parse("2006-01-02", startDate) if err != nil { start, err = time.Parse(time.RFC3339, startDate) if err != nil { return nil, fmt.Errorf("invalid start date: %w", err) } } end, err := time.Parse("2006-01-02", endDate) if err != nil { end, err = time.Parse(time.RFC3339, endDate) if err != nil { return nil, fmt.Errorf("invalid end date: %w", err) } } dbReports, err := s.queries.GetPlayerActivityRange(ctx, dbgen.GetPlayerActivityRangeParams{ UserID: userID, ReportDate: pgtype.Date{Time: start}, ReportDate_2: pgtype.Date{Time: end}, }) if err != nil { return nil, err } activities := make([]domain.PlayerActivityReport, 0, len(dbReports)) for _, r := range dbReports { activities = append(activities, domain.ConvertDBPlayerActivityReport(r)) } return activities, nil } // GetTopPlayersByNetResult returns the top N players by net result in a date range func (s *Store) GetTopPlayersByNetResult(ctx context.Context, startDate, endDate string, limit int) ([]domain.TopPlayerNetResult, error) { start, err := time.Parse("2006-01-02", startDate) if err != nil { start, err = time.Parse(time.RFC3339, startDate) if err != nil { return nil, fmt.Errorf("invalid start date: %w", err) } } end, err := time.Parse("2006-01-02", endDate) if err != nil { end, err = time.Parse(time.RFC3339, endDate) if err != nil { return nil, fmt.Errorf("invalid end date: %w", err) } } rows, err := s.conn.Query(ctx, `SELECT user_id, SUM(net_result) AS total_net FROM virtual_game_player_activity_reports WHERE report_date BETWEEN $1 AND $2 GROUP BY user_id ORDER BY total_net DESC LIMIT $3`, start, end, limit) if err != nil { return nil, fmt.Errorf("failed to fetch top players: %w", err) } defer rows.Close() var topPlayers []domain.TopPlayerNetResult for rows.Next() { var tp domain.TopPlayerNetResult var totalNet pgtype.Numeric if err := rows.Scan(&tp.UserID, &totalNet); err != nil { return nil, err } tp.TotalNet = float64(totalNet.Exp) topPlayers = append(topPlayers, tp) } return topPlayers, nil } // DeletePlayerActivityReport deletes a player activity report by its ID func (s *Store) DeletePlayerActivityReport(ctx context.Context, id int64) error { err := s.queries.DeletePlayerActivityReport(ctx, id) return err }