package rbac

import (
	"fmt"
	"strconv"
	"strings"

	"braces.dev/errtrace"
	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	authsdk "github.com/tidbcloud/tiinf/sdk/auth"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-portal/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-portal/pkg/consts"
)

func (r *rbacServiceImpl) checkIdentityPermission(
	ctx *gin.Context,
	user *entity.User,
	param *authsdk.CheckPermissionParam,
) (bool, string, error) {
	// set org id
	param.ResourceParam = map[string]string{
		consts.RBACOrgIdKey: strconv.FormatUint(user.Org.Id, 10),
	}

	// need cluster_id
	if strings.Contains(param.ResourceUri, consts.RBACClusterIdKey) {
		// check project permissions
		clusterId, err := parseClusterIdFromPath(consts.RelativePath(ctx.Request.URL.Path))
		if err != nil {
			return false, "illegal clusterId", errtrace.Wrap(err)
		}
		param.ResourceParam[consts.RBACClusterIdKey] = clusterId
	}

	if strings.Contains(param.ResourceUri, consts.RBACShadowPoolIdKey) {
		shadowPoolId, err := parseShadowPoolIdFromPath(consts.RelativePath(ctx.Request.URL.Path))
		if err != nil {
			return false, "illegal shadowPoolId", errtrace.Wrap(err)
		}
		param.ResourceParam[consts.RBACShadowPoolIdKey] = shadowPoolId
	}

	if strings.Contains(param.ResourceUri, consts.RBACResourcePoolIdKey) {
		resourcePoolID, err := parseResourcePoolIdFromPath(consts.RelativePath(ctx.Request.URL.Path))
		if err != nil {
			return false, "illegal resourcePoolId", errtrace.Wrap(err)
		}
		param.ResourceParam[consts.RBACResourcePoolIdKey] = resourcePoolID
	}

	// need projectId
	if strings.Contains(param.ResourceUri, consts.RBACProjectIdKey) {
		// full project ids
		if len(user.Projects) == 0 {
			return false, "no project has permission", nil
		}

		// filter no permission project
		projects := make([]*account.Project, 0, len(user.Projects))
		for _, project := range user.Projects {
			param.ResourceParam[consts.RBACProjectIdKey] = strconv.FormatUint(project.Id, 10)

			checkPermissionRet, err := authsdk.CheckIdentityPermission(ctx.Request.Context(), param)
			if err != nil {
				tilog.Error(
					ctx.Request.Context(),
					"check identity permission failed",
					zap.Any("param", param),
					zap.Error(err),
				)
				return false, "check identity permission failed", err
			}

			if checkPermissionRet.HasPermission {
				projects = append(projects, project)
			} else {
				tilog.Warn(ctx.Request.Context(), "check project permission filed",
					zap.String("message", checkPermissionRet.Message),
					zap.Uint64(consts.OrgIdKey, user.Org.Id),
					zap.Uint64(consts.ProjectIdKey, project.Id),
					zap.String("resource_uri", param.ResourceUri),
					zap.String("identity type", string(param.IdentityTye)),
					zap.String("identity", param.IdentityVal))
			}
		}

		if len(projects) == 0 {
			return false, "no project has permission", nil
		}

		user.Projects = projects
		return true, "", nil
	}

	// full project ids
	// auth0/usertoken may not be possible to get projectids
	// with permissions when rbac doesn't need to check the projectid.
	// so we will full project ids
	if len(user.Projects) == 0 && user.IdentityType == authsdk.AuthnIdentityTypeUserToken {
		var err error
		user.Projects, err = r.account.ListProjectsByOrgsInUser(ctx, user.User.Id, user.Org.Id)
		if err != nil {
			return false, "failed to full project list", errtrace.Wrap(err)
		}
	}

	if len(user.Projects) == 0 {
		return false, "no project in this organization", fmt.Errorf("no project in this organization")
	}

	checkPermissionRet, err := authsdk.CheckIdentityPermission(ctx.Request.Context(), param)
	if err != nil {
		return false, "check identity permission failed", err
	}
	return checkPermissionRet.HasPermission, checkPermissionRet.Message, nil
}
