package handlers import ( "context" "errors" "fmt" "time" // "fmt" "strings" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/virtualGame/veli" "github.com/gofiber/fiber/v2" "go.uber.org/zap" ) // GetProviders godoc // @Summary Get game providers // @Description Retrieves the list of VeliGames providers // @Tags Virtual Games - VeliGames // @Accept json // @Produce json // @Param request body domain.ProviderRequest true "Brand ID and paging options" // @Success 200 {object} domain.Response{data=[]domain.ProviderResponse} // @Failure 400 {object} domain.ErrorResponse // @Failure 401 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/veli/providers [post] func (h *Handler) GetProviders(c *fiber.Ctx) error { var req domain.ProviderRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Failed to retrieve providers", Error: err.Error(), }) } if req.BrandID == "" { req.BrandID = h.Cfg.VeliGames.BrandID // default } res, err := h.veliVirtualGameSvc.GetProviders(context.Background(), req) if err != nil { h.InternalServerErrorLogger().Error("Failed to [VeliGameHandler]GetProviders", zap.Any("request", req), zap.Error(err), ) return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{ Message: "Failed to retrieve providers", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Providers retrieved successfully", Data: res, StatusCode: 200, Success: true, }) } // GetGamesByProvider godoc // @Summary Get games by provider // @Description Retrieves games for the specified provider // @Tags Virtual Games - VeliGames // @Accept json // @Produce json // @Param request body domain.GameListRequest true "Brand and Provider ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 502 {object} domain.ErrorResponse // @Router /api/v1/veli/games-list [post] func (h *Handler) GetGamesByProvider(c *fiber.Ctx) error { var req domain.GameListRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } // Default brand if not provided if req.BrandID == "" { req.BrandID = h.Cfg.VeliGames.BrandID } res, err := h.veliVirtualGameSvc.GetGames(context.Background(), req) if err != nil { h.InternalServerErrorLogger().Error("Failed to [VeliGameHandler]GetGames", zap.Any("request", req), zap.Error(err), ) // Handle provider disabled case specifically if strings.Contains(err.Error(), "is disabled") { return c.Status(fiber.StatusForbidden).JSON(domain.ErrorResponse{ Message: "Provider is disabled", Error: err.Error(), }) } // Fallback for other errors return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to retrieve games", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Games retrieved successfully", Data: res, StatusCode: fiber.StatusOK, Success: true, }) } // StartGame godoc // @Summary Start a real game session // @Description Starts a real VeliGames session with the given player and game info // @Tags Virtual Games - VeliGames // @Accept json // @Produce json // @Param request body domain.GameStartRequest true "Start game input" // @Success 200 {object} domain.Response{data=domain.GameStartResponse} // @Failure 400 {object} domain.ErrorResponse // @Failure 502 {object} domain.ErrorResponse // @Router /api/v1/veli/start-game [post] func (h *Handler) StartGame(c *fiber.Ctx) error { var req domain.GameStartRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } // Default brand if not provided if req.BrandID == "" { req.BrandID = h.Cfg.VeliGames.BrandID } // userId := c.Locals("user_id") req.IP = c.IP() req.PlayerID = fmt.Sprintf("%v", c.Locals("user_id")) // 1️⃣ Call StartGame service res, err := h.veliVirtualGameSvc.StartGame(context.Background(), req) if err != nil { h.InternalServerErrorLogger().Error("Failed to [VeliGameHandler]StartGame", zap.Any("request", req), zap.Error(err), ) if strings.Contains(err.Error(), "is disabled") { return c.Status(fiber.StatusForbidden).JSON(domain.ErrorResponse{ Message: "Provider is disabled", Error: err.Error(), }) } return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{ Message: "Failed to start game", Error: err.Error(), }) } // 2️⃣ Game started successfully → Update total_games_played go func() { ctx := context.Background() reportDate := time.Now().Truncate(24 * time.Hour) reportType := "daily" // Increment total_games_played by 1 err := h.orchestrationSvc.UpdateVirtualGameProviderReportByDate( ctx, req.ProviderID, reportDate, reportType, 1, // increment total_games_played by 1 0, 0, 1, ) if err != nil { h.InternalServerErrorLogger().Error("Failed to update total_games_played", zap.String("provider_id", req.ProviderID), zap.Error(err), ) } }() // 3️⃣ Return response to user return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Game started successfully", Data: res, StatusCode: fiber.StatusOK, Success: true, }) } // StartDemoGame godoc // @Summary Start a demo game session // @Description Starts a demo session of the specified game (must support demo mode) // @Tags Virtual Games - VeliGames // @Accept json // @Produce json // @Param request body domain.DemoGameRequest true "Start demo game input" // @Success 200 {object} domain.Response{data=domain.GameStartResponse} // @Failure 400 {object} domain.ErrorResponse // @Failure 502 {object} domain.ErrorResponse // @Router /api/v1/veli/start-demo-game [post] func (h *Handler) StartDemoGame(c *fiber.Ctx) error { var req domain.DemoGameRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } // Default brand if not provided if req.BrandID == "" { req.BrandID = h.Cfg.VeliGames.BrandID } req.IP = c.IP() res, err := h.veliVirtualGameSvc.StartDemoGame(context.Background(), req) if err != nil { h.InternalServerErrorLogger().Error("Failed to [VeliGameHandler]StartDemoGame", zap.Any("request", req), zap.Error(err), ) // Handle provider disabled case specifically if strings.Contains(err.Error(), "is disabled") { return c.Status(fiber.StatusForbidden).JSON(domain.ErrorResponse{ Message: "Provider is disabled", Error: err.Error(), }) } // Fallback for other errors return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{ Message: "Failed to start demo game", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Demo game started successfully", Data: res, StatusCode: fiber.StatusOK, Success: true, }) } func (h *Handler) GetBalance(c *fiber.Ctx) error { var req domain.BalanceRequest if err := c.BodyParser(&req); err != nil { // return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } // Optionally verify signature here... balance, err := h.veliVirtualGameSvc.GetBalance(c.Context(), req) if err != nil { // return fiber.NewError(fiber.StatusInternalServerError, err.Error()) return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Failed to retrieve balance", Error: err.Error(), }) } return c.JSON(balance) } func (h *Handler) PlaceBet(c *fiber.Ctx) error { var req domain.BetRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } // 1️⃣ Process the bet with the external provider res, err := h.veliVirtualGameSvc.ProcessBet(c.Context(), req) if err != nil { if errors.Is(err, veli.ErrDuplicateTransaction) { return fiber.NewError(fiber.StatusConflict, "DUPLICATE_TRANSACTION") } return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Failed to process bet", Error: err.Error(), }) } // 2️⃣ If bet successful → update total_bets in the report go func() { ctx := context.Background() reportDate := time.Now().Truncate(24 * time.Hour) reportType := "daily" // Increment total_bets by the bet amount err := h.orchestrationSvc.UpdateVirtualGameProviderReportByDate( ctx, req.ProviderID, reportDate, reportType, 0, // total_games_played (no change) req.Amount.Amount, // add this bet to total_bets 0, // total_payouts (no change) 0, // total_players (no change) ) if err != nil { h.InternalServerErrorLogger().Error("Failed to update total_bets after bet", zap.String("provider_id", req.ProviderID), zap.Float64("bet_amount", req.Amount.Amount), zap.Error(err), ) } }() // 3️⃣ Return success response return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Bet processed successfully", Data: res, StatusCode: fiber.StatusOK, Success: true, }) } func (h *Handler) RegisterWin(c *fiber.Ctx) error { var req domain.WinRequest if err := c.BodyParser(&req); err != nil { // return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } res, err := h.veliVirtualGameSvc.ProcessWin(c.Context(), req) if err != nil { if errors.Is(err, veli.ErrDuplicateTransaction) { return fiber.NewError(fiber.StatusConflict, "DUPLICATE_TRANSACTION") } // return fiber.NewError(fiber.StatusInternalServerError, err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to process win", Error: err.Error(), }) } return c.JSON(res) } func (h *Handler) CancelTransaction(c *fiber.Ctx) error { var req domain.CancelRequest if err := c.BodyParser(&req); err != nil { // return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } res, err := h.veliVirtualGameSvc.ProcessCancel(c.Context(), req) if err != nil { if errors.Is(err, veli.ErrDuplicateTransaction) { return fiber.NewError(fiber.StatusConflict, "DUPLICATE_TRANSACTION") } // return fiber.NewError(fiber.StatusInternalServerError, err.Error()) return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Failed to process cancel", Error: err.Error(), }) } return c.JSON(res) } // GetGamingActivity godoc // @Summary Get Veli Gaming Activity // @Description Retrieves successfully processed gaming activity transactions (BET, WIN, CANCEL) from Veli Games // @Tags Virtual Games - VeliGames // @Accept json // @Produce json // @Param request body domain.GamingActivityRequest true "Gaming Activity Request" // @Success 200 {object} domain.Response{data=domain.GamingActivityResponse} // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/veli/gaming-activity [post] func (h *Handler) GetGamingActivity(c *fiber.Ctx) error { var req domain.GamingActivityRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request payload", Error: err.Error(), }) } resp, err := h.veliVirtualGameSvc.GetGamingActivity(c.Context(), req) if err != nil { h.InternalServerErrorLogger().Error("Failed to [VeliGameHandler]GetGamingActivity", zap.Any("request", req), zap.Error(err), ) return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to retrieve gaming activity", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Gaming activity retrieved successfully", Data: resp, StatusCode: fiber.StatusOK, Success: true, }) } // GetHugeWins godoc // @Summary Get Veli Huge Wins // @Description Retrieves huge win transactions based on brand configuration (e.g. win > 10000 USD or 100x bet) // @Tags Virtual Games - VeliGames // @Accept json // @Produce json // @Param request body domain.HugeWinsRequest true "Huge Wins Request" // @Success 200 {object} domain.Response{data=domain.HugeWinsResponse} // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/veli/huge-wins [post] func (h *Handler) GetHugeWins(c *fiber.Ctx) error { var req domain.HugeWinsRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request payload", Error: err.Error(), }) } resp, err := h.veliVirtualGameSvc.GetHugeWins(c.Context(), req) if err != nil { h.InternalServerErrorLogger().Error("Failed to [VeliGameHandler]GetHugeWins", zap.Any("request", req), zap.Error(err), ) return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ Message: "Failed to retrieve huge wins", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Huge wins retrieved successfully", Data: resp, StatusCode: fiber.StatusOK, Success: true, }) } // GetCreditBalances godoc // @Summary Get VeliGames credit balances for a brand // @Description Fetches current credit balances per currency for the specified brand // @Tags Virtual Games - VeliGames // @Accept json // @Produce json // @Param brandId query string true "Brand ID" // @Success 200 {object} domain.Response{data=[]domain.CreditBalance} // @Failure 400 {object} domain.ErrorResponse // @Failure 502 {object} domain.ErrorResponse // @Router /api/v1/veli/credit-balances [get] func (h *Handler) GetCreditBalances(c *fiber.Ctx) error { brandID := c.Query("brandId", h.Cfg.VeliGames.BrandID) // Default brand if not provided if brandID == "" { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Brand ID is required", Error: "missing brandId", }) } res, err := h.veliVirtualGameSvc.GetCreditBalances(c.Context(), brandID) if err != nil { h.InternalServerErrorLogger().Error("Failed to [VeliGameHandler]GetCreditBalances", zap.String("brandID", brandID), zap.Error(err), ) return c.Status(fiber.StatusBadGateway).JSON(domain.ErrorResponse{ Message: "Failed to fetch credit balances", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Credit balances fetched successfully", Data: res, StatusCode: fiber.StatusOK, Success: true, }) }