package middleware

import (
	"context"
	"errors"
	"fmt"
	"github.com/tidbcloud/tiinf/sdk/auth"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/request"
	"github.com/pingcap/dbaas/billing2/utils/user"
	"github.com/tidbcloud/tiinf/sdk/rbac"
	"github.com/tidbcloud/tiinf/sdk/rbac/common"
	"github.com/tidbcloud/tiinf/sdk/rbac/useadapter"
	"go.uber.org/zap"
)

func NewRBACMiddleWare(resourceMap map[string]string) gin.HandlerFunc {
	identiyF := func(ctx context.Context, param *common.AccessParam) (string, error) {
		userID := user.GetCurrentUserID()
		if userID == 0 {
			return "", billing_errors.NewInvalidInputError(errors.New("user id not found in context"))
		}
		return fmt.Sprint(userID), nil
	}

	return func(c *gin.Context) {
		if config.Get().GetBool(config.EnableRBACMiddleware) {
			checker := rbac.GetPermissionCheckerWithDefaultHelper(identiyF, resourceMap)
			params := useadapter.GenAccessParam4Gin(c)
			if !request.SourceFromConsole(c.Request) {
				log.Info("rbac check skipped in request", zap.Uint("userID", user.GetCurrentUserID()), zap.String("userEmail", user.GetCurrentUserEmail()), zap.Any("params", params.Metadata))
				orgID, err := auth.GetAccessOrgIdFromGinContext(c)
				if err == nil && orgID != 0 {
					log.Info("add auth info for open api access", zap.Uint64("orgID", orgID), zap.Uint("userID", user.GetCurrentUserID()), zap.String("userEmail", user.GetCurrentUserEmail()))
					c.Set(auth.IdentityInfoKey, &auth.AuthnIdentityInfo{IdentityType: auth.AuthnIdentityTypeUserToken, OrgID: orgID})
				}
				c.Next()
				return
			}
			result, err := checker.Handle(c.Request.Context(), params)
			if err != nil || result.Code != common.RSP_CODE_RELEASE {
				log.Info("rbac check failed in request", zap.Uint("userID", user.GetCurrentUserID()), zap.String("userEmail", user.GetCurrentUserEmail()), zap.Any("params", params.Metadata))
				if config.Get().GetBool(config.EnableRBAC) {
					c.JSON(http.StatusForbidden, map[string]string{
						"error": "permission check intercept",
					})
					c.Abort()
					return
				}
			} else {
				log.Info("rbac check success in request", zap.Uint("userID", user.GetCurrentUserID()), zap.String("userEmail", user.GetCurrentUserEmail()), zap.Any("params", params.Metadata))
			}
			c.Next()
		} else {
			c.Next()
		}
	}
}
