package rbac

import (
	"context"
	"errors"
	"fmt"
	"time"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/internal-portal/apiserver"
	apiAuth "github.com/tidbcloud/internal-portal/apiserver/auth"
	"github.com/tidbcloud/internal-portal/core-portal/auth"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/rbac"
	"github.com/tidbcloud/tiinf/sdk/rbac/useadapter"
	"go.uber.org/zap"
	"google.golang.org/grpc"

	"github.com/tidbcloud/tiinf/sdk/rbac/common"
	"github.com/tidbcloud/tiinf/sdk/rbac/helperimpl"
)

var pathPatternResourceMap = map[string]string{
	"/api/v1/orgs/(\\d+)/projects/(\\d+)/user_alert":                   "orgs/{org_id}/projects/{project_id}/user_alert",
	"delete:/api/v1/orgs/(\\d+)/projects/(\\d+)/user_alert/(\\d+)":     "orgs/{org_id}/projects/{project_id}/user_alert/{user_alert_id}",
	"get:/api/userdebug/v1/(\\d+)/features":                            "userdebug/v1/orgs/{org_id}/features",
	"get:/api/userdebug/v1/(\\d+)/authorizations":                      "userdebug/v1/orgs/{org_id}/authorizations",
	"get:/api/userdebug/v1/(\\d+)/(\\d+)/authorization/details/(\\d+)": "userdebug/v1/orgs/{org_id}/{cluster_id}/authorization/details/{auth_id}",
	"/api/userdebug/v1/(\\d+)/(\\d+)/authorization":                    "userdebug/v1/orgs/{org_id}/{cluster_id}/authorizationt",
	"get:/api/userdebug/v1/(\\d+)/(\\d+)/authorization/records":        "userdebug/v1/orgs/{org_id}/{cluster_id}/authorization/records",
	"get:/api/userdebug/v1/(\\d+)/(\\d+)/authorization/records/(\\d+)": "userdebug/v1/orgs/{org_id}/{cluster_id}/authorization/records/{auth_id}",
}

var pathWhitelist = []string{
	"/api/useralerts/v1/alert",
}

func init() {
	helperimpl.DefaultCachedPermissionHelper.SetReadTimeout(6 * time.Second)
}

func Useadapter() func(ctx *gin.Context) {
	handler := useadapter.GinPermissionCheckInterceptor(
		ginGetIdentityFunc,
		pathPatternResourceMap,
		pathWhitelist...)

	return func(ctx *gin.Context) {
		if apiAuth.IsAuthWhitelist(ctx) {
			user, err := apiserver.GetUserFromContext(ctx)
			if err == nil && user.ID == 1 {
				return
			}
		}
		handler(ctx)
	}
}

func UnaryServerInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		logger := tilog.WithContextL(ctx)
		if !useadapter.WhetherRequestComeFromGrpcGateway(ctx) {
			logger.Info("rbac permission check ignore, request not come from grpc gateway")
			return handler(ctx, req)
		}
		checker := getPermissionChecker()
		param := useadapter.GenAccessParam4GrpcGateway(ctx)
		result, err := checker.Handle(ctx, param)
		logger.Info("rbac permission check finished", zap.Any("result", result), zap.Any("err", err), zap.Any("param", param))

		if IsCloseRbacAccessCheck() {
			logger.Info("rbac permission check closed, do not check result")
			return handler(ctx, req)
		}

		if err != nil {
			return nil, status.Error(codes.PermissionDenied, err.Error())
		}
		if result.Code != common.RSP_CODE_RELEASE {
			return nil, status.Error(codes.PermissionDenied, "permission denied")
		}

		return handler(ctx, req)
	}
}

func StreamServerInterceptor() grpc.StreamServerInterceptor {
	return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
		logger := tilog.WithContextL(ss.Context())
		if !useadapter.WhetherRequestComeFromGrpcGateway(ss.Context()) {
			logger.Info("rbac permission check ignore, request not come from grpc gateway")
			return handler(srv, ss)
		}
		checker := getPermissionChecker()
		param := useadapter.GenAccessParam4GrpcGateway(ss.Context())
		result, err := checker.Handle(ss.Context(), param)
		logger.Info("rbac permission check finished", zap.Any("result", result), zap.Any("err", err), zap.Any("param", param))

		if IsCloseRbacAccessCheck() {
			logger.Info("rbac permission check closed, do not check result")
			return handler(srv, ss)
		}

		if err != nil {
			return status.Error(codes.PermissionDenied, err.Error())
		}
		if result.Code != common.RSP_CODE_RELEASE {
			return status.Error(codes.PermissionDenied, "permission denied")
		}
		return handler(srv, ss)
	}
}

func getPermissionChecker() rbac.PermissionChecker {
	return rbac.GetPermissionCheckerWithDefaultHelper(
		getIdentityFunc,
		pathPatternResourceMap,
		pathWhitelist...)
}

func getIdentityFunc(ctx context.Context, _ *common.AccessParam) (string, error) {

	u, err := auth.GetUserFromContext(ctx)
	if err != nil {
		return "", errors.New("get user from context error")
	}
	return fmt.Sprintf("%d", u.ID), nil
}

func ginGetIdentityFunc(ctx context.Context, param *common.AccessParam) (string, error) {
	c := param.Req.(*gin.Context)
	u, err := apiserver.GetUserFromContext(c)
	if err != nil {
		//return "", errors.New("get user from context error")
		return "", err
	}
	return fmt.Sprintf("%d", u.ID), nil
}

func IsCloseOriginAccessCheck() bool {
	return false
}

func IsCloseRbacAccessCheck() bool {
	return true
}
