package rbac

import (
	"context"
	"testing"

	"github.com/go-jose/go-jose/v4/jwt"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/grafana/authlib/authn"
	authzv1 "github.com/grafana/authlib/authz/proto/v1"
	types "github.com/grafana/authlib/types"
	"github.com/grafana/grafana/pkg/services/accesscontrol"
	"github.com/grafana/grafana/pkg/services/authz/rbac/store"
)

// TestReproWildcardBypass demonstrates the IAM LIST authorization bypass
// for folder-scoped CRDs (mapper-miss resources) when a user has a
// resource-type wildcard permission (scope: "*") but NO folder-level access.
//
// This test calls the public gRPC List handler (s.List) which exercises the
// full API path: validateListRequest → getIdentityPermissions → listPermission.
//
// On the VULNERABLE version, listPermission checks scopeMap["*"] BEFORE
// checking whether the resource is a mapper-miss (folder-scoped CRD), so it
// returns { All: true } — allowing listing of all objects across all folders
// without folder-level authorization.
//
// On the FIXED version, listPermission forks to listPermissionWithFolderAuthz
// for mapper-miss resources BEFORE the scopeMap["*"] check, which requires
// both a stack role (scopeMap[""]) AND folder-level authorization. Since the
// user only has scopeMap["*"] (no stack role) and no folder grants, the
// response is empty (All: false).
func TestReproWildcardBypass(t *testing.T) {
	const group = "widget.ext.grafana.app"

	s := setupService()
	userID := &store.UserIdentifiers{UID: "test-uid", ID: 1}

	// User has ONLY a wildcard resource permission (scope=*) for the
	// folder-scoped CRD, but NO folder-level access (no folders:read).
	fStore := &fakeStore{
		userID: userID,
		userPermissions: []accesscontrol.Permission{
			{
				Action: group + "/widgets:get",
				Scope:  "*",
				Kind:   "*",
			},
		},
		folders:        []store.Folder{{UID: "f1"}},
		disableNsCheck: true,
	}
	s.store = fStore
	s.permissionStore = fStore
	s.folderStore = fStore
	s.identityStore = &fakeIdentityStore{disableNsCheck: true}

	// Pre-populate folder cache so buildFolderTree is not called.
	s.folderCache.Set(context.Background(), folderCacheKey("org-12"), newFolderTree([]store.Folder{{UID: "f1"}}))

	// Verify this is a mapper-miss resource (folder-scoped CRD).
	_, mapperFound := s.mapper.Get(group, "widgets", "")
	t.Logf("mapper.Get(%q, %q, %q) found=%v (expected false = mapper-miss)", group, "widgets", "", mapperFound)
	assert.False(t, mapperFound, "widget.ext.grafana.app/widgets must be a mapper-miss (folder-scoped CRD)")

	// Set up auth context (required by validateListRequest → validateNamespace).
	callingService := authn.NewAccessTokenAuthInfo(authn.Claims[authn.AccessTokenClaims]{
		Claims: jwt.Claims{
			Subject:  types.NewTypeID(types.TypeAccessPolicy, "some-service"),
			Audience: []string{"authzservice"},
		},
		Rest: authn.AccessTokenClaims{Namespace: "org-12"},
	})
	ctx := types.WithAuthInfo(context.Background(), callingService)

	// Send a LIST request for the folder-scoped CRD resource via the public
	// gRPC List handler. This exercises:
	//   validateListRequest → getIdentityPermissions → getUserPermissions →
	//   getScopeMap → resolveScopeMap → listPermission
	resp, err := s.List(ctx, &authzv1.ListRequest{
		Namespace: "org-12",
		Subject:   "user:test-uid",
		Group:     group,
		Resource:  "widgets",
		Verb:      "list",
	})
	require.NoError(t, err)

	t.Logf("List response: All=%v, Folders=%v, Items=%v", resp.All, resp.Folders, resp.Items)

	if resp.All {
		t.Logf("VULNERABLE: LIST request for folder-scoped CRD returned All=true with only wildcard resource permission and NO folder-level authorization — BYPASS CONFIRMED")
	} else {
		t.Logf("FIXED: LIST request for folder-scoped CRD did NOT return All=true — folder-level authorization enforced")
	}
}
