package bet import ( "context" "encoding/json" "errors" "fmt" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "go.uber.org/zap" ) func newBetResultNotification(userID int64, level domain.NotificationLevel, channel domain.DeliveryChannel, headline, message string, metadata any) *domain.Notification { raw, _ := json.Marshal(metadata) return &domain.Notification{ RecipientID: userID, DeliveryStatus: domain.DeliveryStatusPending, IsRead: false, Type: domain.NOTIFICATION_TYPE_BET_RESULT, Level: level, Reciever: domain.NotificationRecieverSideCustomer, DeliveryChannel: channel, Payload: domain.NotificationPayload{ Headline: headline, Message: message, }, Priority: 2, Metadata: raw, } } type SendResultNotificationParam struct { BetID int64 Status domain.OutcomeStatus UserID int64 WinningAmount domain.Currency Extra string SendEmail bool SendSMS bool } func (p SendResultNotificationParam) Validate() error { if p.BetID == 0 { return errors.New("BetID is required") } if p.UserID == 0 { return errors.New("UserID is required") } return nil } func shouldSend(channel domain.DeliveryChannel, sendEmail, sendSMS bool) bool { switch { case channel == domain.DeliveryChannelEmail && sendEmail: return true case channel == domain.DeliveryChannelSMS && sendSMS: return true case channel == domain.DeliveryChannelInApp: return true default: return false } } func (s *Service) SendWinningStatusNotification(ctx context.Context, param SendResultNotificationParam) error { if err := param.Validate(); err != nil { return err } var headline string var message string switch param.Status { case domain.OUTCOME_STATUS_WIN: headline = fmt.Sprintf("Bet #%v Won!", param.BetID) message = fmt.Sprintf( "Congratulations! Your bet #%v has won. %.2f has been credited to your wallet.", param.BetID, param.WinningAmount.Float32(), ) case domain.OUTCOME_STATUS_HALF: headline = fmt.Sprintf("Bet #%v Half-Win", param.BetID) message = fmt.Sprintf( "Your bet #%v resulted in a half-win. %.2f has been credited to your wallet.", param.BetID, param.WinningAmount.Float32(), ) case domain.OUTCOME_STATUS_VOID: headline = fmt.Sprintf("Bet #%v Refunded", param.BetID) message = fmt.Sprintf( "Your bet #%v has been voided. %.2f has been refunded to your wallet.", param.BetID, param.WinningAmount.Float32(), ) default: return fmt.Errorf("unsupported status: %v", param.Status) } for _, channel := range []domain.DeliveryChannel{ domain.DeliveryChannelInApp, domain.DeliveryChannelEmail, domain.DeliveryChannelSMS, } { if !shouldSend(channel, param.SendEmail, param.SendSMS) { continue } n := newBetResultNotification(param.UserID, domain.NotificationLevelSuccess, channel, headline, message, map[string]any{ "winning_amount": param.WinningAmount.Float32(), "status": param.Status, "more": param.Extra, }) if err := s.notificationSvc.SendNotification(ctx, n); err != nil { return err } } return nil } func (s *Service) SendLosingStatusNotification(ctx context.Context, param SendResultNotificationParam) error { if err := param.Validate(); err != nil { return err } var headline string var message string switch param.Status { case domain.OUTCOME_STATUS_LOSS: headline = fmt.Sprintf("Bet #%v Lost", param.BetID) message = "Unfortunately, your bet did not win this time. Better luck next time!" default: return fmt.Errorf("unsupported status: %v", param.Status) } for _, channel := range []domain.DeliveryChannel{ domain.DeliveryChannelInApp, domain.DeliveryChannelEmail, domain.DeliveryChannelSMS, } { if !shouldSend(channel, param.SendEmail, param.SendSMS) { continue } n := newBetResultNotification(param.UserID, domain.NotificationLevelWarning, channel, headline, message, map[string]any{ "status": param.Status, "more": param.Extra, }) if err := s.notificationSvc.SendNotification(ctx, n); err != nil { return err } } return nil } func (s *Service) SendErrorStatusNotification(ctx context.Context, betID int64, status domain.OutcomeStatus, userID int64, extra string) error { var headline string var message string switch status { case domain.OUTCOME_STATUS_ERROR, domain.OUTCOME_STATUS_PENDING: headline = fmt.Sprintf("Bet #%v Processing Issue", betID) message = "We encountered a problem while processing your bet. Our team is working to resolve it as soon as possible." default: return fmt.Errorf("unsupported status: %v", status) } for _, channel := range []domain.DeliveryChannel{ domain.DeliveryChannelInApp, domain.DeliveryChannelEmail, } { n := newBetResultNotification(userID, domain.NotificationLevelError, channel, headline, message, map[string]any{ "status": status, "more": extra, }) if err := s.notificationSvc.SendNotification(ctx, n); err != nil { return err } } return nil } func (s *Service) SendAdminErrorNotification(ctx context.Context, betID int64, status domain.OutcomeStatus, extra string, companyID int64) error { var headline string var message string switch status { case domain.OUTCOME_STATUS_ERROR, domain.OUTCOME_STATUS_PENDING: headline = fmt.Sprintf("Processing Error for Bet #%v", betID) message = "A processing error occurred with this bet. Please review and take corrective action." default: return fmt.Errorf("unsupported status: %v", status) } super_admin_users, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{ Role: string(domain.RoleSuperAdmin), }) if err != nil { s.mongoLogger.Error("failed to get super_admin recipients", zap.Error(err), zap.Time("timestamp", time.Now()), ) return err } admin_users, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{ Role: string(domain.RoleAdmin), CompanyID: domain.ValidInt64{ Value: companyID, Valid: true, }, }) if err != nil { s.mongoLogger.Error("failed to get admin recipients", zap.Error(err), zap.Time("timestamp", time.Now()), ) return err } users := append(super_admin_users, admin_users...) for _, user := range users { for _, channel := range []domain.DeliveryChannel{ domain.DeliveryChannelInApp, // domain.DeliveryChannelEmail, } { n := newBetResultNotification(user.ID, domain.NotificationLevelError, channel, headline, message, map[string]any{ "status": status, "more": extra, }) if err := s.notificationSvc.SendNotification(ctx, n); err != nil { return err } } } return nil } func (s *Service) SendAdminLargeBetNotification(ctx context.Context, betID int64, totalWinnings float32, extra string, companyID int64) error { headline := "SYSTEM WARNING: High Risk Bet" message := fmt.Sprintf("Bet #%d has been created with %v payout", betID, totalWinnings) super_admin_users, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{ Role: string(domain.RoleSuperAdmin), }) if err != nil { s.mongoLogger.Error("failed to get super_admin recipients", zap.Error(err), zap.Time("timestamp", time.Now()), ) return err } admin_users, _, err := s.userSvc.GetAllUsers(ctx, domain.UserFilter{ Role: string(domain.RoleAdmin), CompanyID: domain.ValidInt64{ Value: companyID, Valid: true, }, }) if err != nil { s.mongoLogger.Error("failed to get admin recipients", zap.Error(err), zap.Time("timestamp", time.Now()), ) return err } users := append(super_admin_users, admin_users...) for _, user := range users { for _, channel := range []domain.DeliveryChannel{ domain.DeliveryChannelInApp, // domain.DeliveryChannelEmail, } { raw, _ := json.Marshal(map[string]any{ "winnings": totalWinnings, "more": extra, }) n := &domain.Notification{ RecipientID: user.ID, DeliveryStatus: domain.DeliveryStatusPending, IsRead: false, Type: domain.NOTIFICATION_TYPE_ADMIN_ALERT, Level: domain.NotificationLevelWarning, Reciever: domain.NotificationRecieverSideAdmin, DeliveryChannel: channel, Payload: domain.NotificationPayload{ Headline: headline, Message: message, }, Priority: 2, Metadata: raw, } // n := newBetResultNotification(user.ID, domain.NotificationLevelWarning, channel, headline, message) if err := s.notificationSvc.SendNotification(ctx, n); err != nil { return err } } } return nil }