//nolint:lll
package changefeed

import (
	"context"
	"fmt"
	"strings"

	"github.com/pingcap/log"
	"github.com/tidbcloud/dataflow-service/app/controllers/open"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/tiinf/sdk/auth"
	"go.uber.org/zap"
	"google.golang.org/grpc/metadata"
)

// Constants for RBAC resource URIs. These strings must match the definitions
// in the RBAC policy service. They are used to identify which permission is being checked.
const (
	ListChangefeedsReq      = "openapi/changefeed/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/changefeeds/list"
	CreateChangefeedReq     = "openapi/changefeed/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/changefeeds/create"
	EditChangefeedReq       = "openapi/changefeed/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/changefeeds/edit"
	DeleteChangefeedReq     = "openapi/changefeed/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/changefeeds/delete"
	ScaleChangefeedReq      = "openapi/changefeed/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/changefeeds/scale"
	ListChangefeedRCUReq    = "openapi/changefeed/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/changefeeds/listRCUs"
	GetChangefeedReq        = "openapi/changefeed/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/changefeeds/get"
	PauseChangefeedReq      = "openapi/changefeed/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/changefeeds/pause"
	ResumeChangefeedReq     = "openapi/changefeed/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/changefeeds/resume"
	GetChangefeedFiltersReq = "openapi/changefeed/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/changefeeds/getEventFilters"
	SetChangefeedFiltersReq = "openapi/changefeed/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/changefeeds/setEventFilters"
)

// rbacChecker is a helper struct that encapsulates the logic for permission checking.
// It holds a client to the RBAC service.
type rbacChecker struct {
	rbacClient RBACClient
}

// nwRBACChecker creates a new rbacChecker with a given RBAC client.
// This constructor is used to inject the dependency.
func nwRBACChecker(rbacClient RBACClient) *rbacChecker {
	return &rbacChecker{rbacClient: rbacClient}
}

// checkPermission performs the actual permission check against the RBAC service.
// It extracts the API key from the context metadata and uses it to verify permissions for the given resource.
func (c *rbacChecker) checkPermission(ctx context.Context, resourceURI string,
	orgID, projectID, clusterID uint64,
) bizerr.BizError {
	// A valid API key is expected to be in the gRPC incoming metadata,
	// typically set by an authentication middleware.
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return bizerr.New(bizerr.ErrCodePermission, "missing permission metadata")
	}
	apiKeysValues := md.Get(strings.ToLower(open.OpenAPIPublicKey))
	if len(apiKeysValues) != 1 {
		return bizerr.New(bizerr.ErrCodePermission, "invalid permission metadata")
	}
	apiKey := apiKeysValues[0]

	// Prepare the parameters for the RBAC check call.
	param := auth.CheckPermissionParam{
		IdentityTye: auth.AccessIdentityTypeApiKeyAK,
		IdentityVal: apiKey,
		ResourceUri: resourceURI,
		ResourceParam: map[string]string{
			"org_id":     fmt.Sprint(orgID),
			"project_id": fmt.Sprint(projectID),
			"cluster_id": fmt.Sprint(clusterID),
		},
	}

	// Call the underlying RBAC client.
	result, err := c.rbacClient.CheckIdentityPermission(ctx, &param)
	if err != nil {
		// Log the internal error and return a generic error to the user to avoid exposing implementation details.
		log.Error("rbac check error",
			zap.String("resourceUri", resourceURI),
			zap.String("orgId", fmt.Sprint(orgID)),
			zap.String("projectId", fmt.Sprint(projectID)),
			zap.String("clusterId", fmt.Sprint(clusterID)),
			zap.Error(err))
		return bizerr.New(bizerr.ErrCodeGRPC, "failed to check permission")
	}

	// If the RBAC service denies permission, return a specific business error.
	if !result.HasPermission {
		return bizerr.New(bizerr.ErrCodePermission, "no permission %s", result.Message)
	}
	// If permission is granted, return nil indicating success.
	return nil
}
