package api

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"math/rand"
	"net/http"
	"net/http/httptest"
	"net/url"
	"slices"
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"

	"github.com/grafana/grafana/pkg/infra/log"
	ac "github.com/grafana/grafana/pkg/services/accesscontrol"
	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
	"github.com/grafana/grafana/pkg/services/dashboards"
	"github.com/grafana/grafana/pkg/services/datasources"
	"github.com/grafana/grafana/pkg/services/featuremgmt"
	"github.com/grafana/grafana/pkg/services/folder"
	"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
	apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
	"github.com/grafana/grafana/pkg/services/ngalert/models"
	"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
	"github.com/grafana/grafana/pkg/services/ngalert/store"
	"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
	"github.com/grafana/grafana/pkg/services/user"
	"github.com/grafana/grafana/pkg/services/user/usertest"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/util"
	"github.com/grafana/grafana/pkg/util/cmputil"
	"github.com/grafana/grafana/pkg/web"
)

func TestRouteDeleteAlertRules(t *testing.T) {
	getRecordedCommand := func(ruleStore *fakes.RuleStore) []fakes.GenericRecordedQuery {
		results := ruleStore.GetRecordedCommands(func(cmd any) (any, bool) {
			c, ok := cmd.(fakes.GenericRecordedQuery)
			if !ok || c.Name != "DeleteAlertRulesByUID" {
				return nil, false
			}
			return c, ok
		})
		var result []fakes.GenericRecordedQuery
		for _, cmd := range results {
			result = append(result, cmd.(fakes.GenericRecordedQuery))
		}
		return result
	}

	assertRulesDeleted := func(t *testing.T, expectedRules []*models.AlertRule, ruleStore *fakes.RuleStore) {
		deleteCommands := getRecordedCommand(ruleStore)
		require.Len(t, deleteCommands, 1)
		cmd := deleteCommands[0]
		actualUIDs := cmd.Params[3].([]string)
		require.Len(t, actualUIDs, len(expectedRules))
		for _, rule := range expectedRules {
			require.Containsf(t, actualUIDs, rule.UID, "Rule %s was expected to be deleted but it wasn't", rule.UID)
		}
	}

	orgID := rand.Int63()
	folder := randFolder()
	gen := models.RuleGen.With(models.RuleGen.WithOrgID(orgID))

	initFakeRuleStore := func(t *testing.T) *fakes.RuleStore {
		ruleStore := fakes.NewRuleStore(t)
		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
		// add random data
		ruleStore.PutRule(context.Background(), gen.GenerateManyRef(1, 5)...)
		return ruleStore
	}

	t.Run("when fine-grained access is enabled", func(t *testing.T) {
		t.Run("and group argument is empty", func(t *testing.T) {
			t.Run("allow deleting without access to datasource", func(t *testing.T) {
				ruleStore := initFakeRuleStore(t)
				provisioningStore := fakes.NewFakeProvisioningStore()

				folderGen := gen.With(gen.WithNamespace(folder.ToFolderReference()))

				authorizedRulesInFolder := folderGen.With(gen.WithGroupPrefix("authz-")).GenerateManyRef(1, 5)

				ruleStore.PutRule(context.Background(), authorizedRulesInFolder...)

				permissions := createPermissionsForRulesWithoutDS(authorizedRulesInFolder, orgID)
				requestCtx := createRequestContextWithPerms(orgID, permissions, nil)

				response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.UID, "")

				require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
				assertRulesDeleted(t, authorizedRulesInFolder, ruleStore)
			})
			t.Run("return Forbidden if user is not authorized to access any group in the folder", func(t *testing.T) {
				ruleStore := initFakeRuleStore(t)
				ruleStore.PutRule(context.Background(), gen.With(gen.WithNamespace(folder.ToFolderReference())).GenerateManyRef(1, 5)...)

				request := createRequestContextWithPerms(orgID, map[int64]map[string][]string{}, nil)

				response := createService(ruleStore, nil).RouteDeleteAlertRules(request, folder.UID, "")
				require.Equalf(t, http.StatusForbidden, response.Status(), "Expected 403 but got %d: %v", response.Status(), string(response.Body()))

				require.Empty(t, getRecordedCommand(ruleStore))
			})
			t.Run("delete only non-provisioned groups that user is authorized", func(t *testing.T) {
				ruleStore := initFakeRuleStore(t)
				provisioningStore := fakes.NewFakeProvisioningStore()

				folderGen := gen.With(gen.WithNamespace(folder.ToFolderReference()))

				authorizedRulesInFolder := folderGen.With(gen.WithGroupPrefix("authz-")).GenerateManyRef(1, 5)

				provisionedRulesInFolder := folderGen.With(gen.WithGroupPrefix("provisioned-")).GenerateManyRef(1, 5)
				for _, rule := range provisionedRulesInFolder {
					err := provisioningStore.SetProvenance(context.Background(), rule, orgID, models.ProvenanceAPI)
					require.NoError(t, err)
				}

				ruleStore.PutRule(context.Background(), authorizedRulesInFolder...)
				ruleStore.PutRule(context.Background(), provisionedRulesInFolder...)

				permissions := createPermissionsForRules(append(authorizedRulesInFolder, provisionedRulesInFolder...), orgID)
				requestCtx := createRequestContextWithPerms(orgID, permissions, nil)

				response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.UID, "")

				require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
				assertRulesDeleted(t, authorizedRulesInFolder, ruleStore)
			})
			t.Run("return 400 if all rules user can access are provisioned", func(t *testing.T) {
				ruleStore := initFakeRuleStore(t)
				provisioningStore := fakes.NewFakeProvisioningStore()

				folderGen := gen.With(gen.WithNamespace(folder.ToFolderReference()))

				provisionedRulesInFolder := folderGen.With(gen.WithSameGroup()).GenerateManyRef(1, 5)
				err := provisioningStore.SetProvenance(context.Background(), provisionedRulesInFolder[0], orgID, models.ProvenanceAPI)
				require.NoError(t, err)

				ruleStore.PutRule(context.Background(), provisionedRulesInFolder...)

				permissions := createPermissionsForRules(provisionedRulesInFolder, orgID)
				requestCtx := createRequestContextWithPerms(orgID, permissions, nil)

				response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.UID, "")

				require.Equalf(t, 400, response.Status(), "Expected 400 but got %d: %v", response.Status(), string(response.Body()))
				require.Empty(t, getRecordedCommand(ruleStore))
			})
			t.Run("should return 202 if folder is empty", func(t *testing.T) {
				ruleStore := initFakeRuleStore(t)

				requestCtx := createRequestContext(orgID, nil)
				response := createService(ruleStore, nil).RouteDeleteAlertRules(requestCtx, folder.UID, "")

				require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
				require.Empty(t, getRecordedCommand(ruleStore))
			})
		})
		t.Run("and group argument is not empty", func(t *testing.T) {
			t.Run("return Forbidden if user is not authorized to access the group", func(t *testing.T) {
				ruleStore := initFakeRuleStore(t)

				groupGen := gen.With(gen.WithNamespace(folder.ToFolderReference()), gen.WithSameGroup())

				authorizedRulesInGroup := groupGen.GenerateManyRef(1, 5)
				ruleStore.PutRule(context.Background(), authorizedRulesInGroup...)

				permissions := createPermissionsForRules([]*models.AlertRule{}, orgID)
				requestCtx := createRequestContextWithPerms(orgID, permissions, nil)

				response := createService(ruleStore, nil).RouteDeleteAlertRules(requestCtx, folder.UID, authorizedRulesInGroup[0].RuleGroup)

				require.Equalf(t, http.StatusForbidden, response.Status(), "Expected 403 but got %d: %v", response.Status(), string(response.Body()))
				deleteCommands := getRecordedCommand(ruleStore)
				require.Empty(t, deleteCommands)
			})
			t.Run("return 400 if group is provisioned", func(t *testing.T) {
				ruleStore := initFakeRuleStore(t)
				provisioningStore := fakes.NewFakeProvisioningStore()

				groupGen := gen.With(gen.WithNamespace(folder.ToFolderReference()), gen.WithSameGroup())

				provisionedRulesInFolder := groupGen.GenerateManyRef(1, 5)
				err := provisioningStore.SetProvenance(context.Background(), provisionedRulesInFolder[0], orgID, models.ProvenanceAPI)
				require.NoError(t, err)

				ruleStore.PutRule(context.Background(), provisionedRulesInFolder...)

				permissions := createPermissionsForRules(provisionedRulesInFolder, orgID)
				requestCtx := createRequestContextWithPerms(orgID, permissions, nil)

				response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.UID, provisionedRulesInFolder[0].RuleGroup)

				require.Equalf(t, 400, response.Status(), "Expected 400 but got %d: %v", response.Status(), string(response.Body()))
				deleteCommands := getRecordedCommand(ruleStore)
				require.Empty(t, deleteCommands)
			})
		})
	})
}

func TestRouteGetNamespaceRulesConfig(t *testing.T) {
	gen := models.RuleGen
	t.Run("fine-grained access is enabled", func(t *testing.T) {
		t.Run("should return all rules, with or without data source access", func(t *testing.T) {
			orgID := rand.Int63()
			folder := randFolder()
			ruleStore := fakes.NewRuleStore(t)
			ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
			folderGen := gen.With(gen.WithOrgID(orgID), gen.WithNamespace(folder.ToFolderReference()), gen.WithUpdatedBy(util.Pointer(models.UserUID("test-user"))))
			queryAccessRules := folderGen.GenerateManyRef(2, 6)
			ruleStore.PutRule(context.Background(), queryAccessRules...)
			noQueryAccessRules := folderGen.GenerateManyRef(2, 6)
			ruleStore.PutRule(context.Background(), noQueryAccessRules...)

			allRules := make([]*models.AlertRule, 0, len(queryAccessRules)+len(noQueryAccessRules))
			allRules = append(allRules, queryAccessRules...)
			allRules = append(allRules, noQueryAccessRules...)

			permissions := createPermissionsForRules(queryAccessRules, orgID)
			req := createRequestContextWithPerms(orgID, permissions, nil)

			fakeUserService := usertest.NewUserServiceFake()
			userUids := make([]string, 0)
			for _, rule := range allRules {
				if rule.UpdatedBy != nil {
					userUids = append(userUids, string(*rule.UpdatedBy))
				}
			}
			fakeUserServiceResponse := []*user.User{}
			for i, uid := range userUids {
				fakeUserServiceResponse = append(fakeUserServiceResponse, &user.User{ID: int64(i + 1), UID: uid})
			}
			fakeUserService.ExpectedListUsersByIdOrUid = fakeUserServiceResponse

			svc := createService(ruleStore, fakeUserService)
			response := svc.RouteGetNamespaceRulesConfig(req, folder.UID)

			require.Equal(t, http.StatusAccepted, response.Status())
			require.Equal(t, 1, len(fakeUserService.ListUsersByIdOrUidCalls)) // only one call to the user service
			result := &apimodels.NamespaceConfigResponse{}
			require.NoError(t, json.Unmarshal(response.Body(), result))
			require.NotNil(t, result)
			for namespace, groups := range *result {
				require.Equal(t, folder.Fullpath, namespace)
				for _, group := range groups {
				grouploop:
					for _, actualRule := range group.Rules {
						for i, expected := range allRules {
							if actualRule.GrafanaManagedAlert.UID == expected.UID {
								allRules = append(allRules[:i], allRules[i+1:]...)
								continue grouploop
							}
						}
						assert.Failf(t, "rule in a group was not found in expected", "rule %s group %s", actualRule.GrafanaManagedAlert.Title, group.Name)
					}
				}
			}
			assert.Emptyf(t, allRules, "not all expected rules were returned")
		})
	})
	t.Run("should return the provenance of the alert rules", func(t *testing.T) {
		orgID := rand.Int63()
		folder := randFolder()
		ruleStore := fakes.NewRuleStore(t)
		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
		expectedRules := gen.With(gen.WithOrgID(orgID), gen.WithNamespace(folder.ToFolderReference())).GenerateManyRef(2, 6)
		ruleStore.PutRule(context.Background(), expectedRules...)

		fakeUserService := usertest.NewUserServiceFake()
		userUids := make([]string, 0)
		for _, rule := range expectedRules {
			if rule.UpdatedBy != nil {
				userUids = append(userUids, string(*rule.UpdatedBy))
			}
		}
		fakeUserServiceResponse := []*user.User{}
		for i, uid := range userUids {
			fakeUserServiceResponse = append(fakeUserServiceResponse, &user.User{ID: int64(i + 1), UID: uid})
		}
		fakeUserService.ExpectedListUsersByIdOrUid = fakeUserServiceResponse

		svc := createService(ruleStore, fakeUserService)

		// add provenance to the first generated rule
		rule := &models.AlertRule{
			UID: expectedRules[0].UID,
		}
		err := svc.provenanceStore.SetProvenance(context.Background(), rule, orgID, models.ProvenanceAPI)
		require.NoError(t, err)

		perms := createPermissionsForRules(expectedRules, orgID)
		req := createRequestContextWithPerms(orgID, perms, nil)
		response := svc.RouteGetNamespaceRulesConfig(req, folder.UID)

		require.Equal(t, http.StatusAccepted, response.Status())
		if len(userUids) > 0 {
			require.Equal(t, 1, len(fakeUserService.ListUsersByIdOrUidCalls))
		} else {
			require.Equal(t, 0, len(fakeUserService.ListUsersByIdOrUidCalls))
		}
		result := &apimodels.NamespaceConfigResponse{}
		require.NoError(t, json.Unmarshal(response.Body(), result))
		require.NotNil(t, result)
		found := false
		for namespace, groups := range *result {
			require.Equal(t, folder.Fullpath, namespace)
			for _, group := range groups {
				for _, actualRule := range group.Rules {
					if actualRule.GrafanaManagedAlert.UID == expectedRules[0].UID {
						require.Equal(t, apimodels.Provenance(models.ProvenanceAPI), actualRule.GrafanaManagedAlert.Provenance)
						found = true
					} else {
						require.Equal(t, apimodels.Provenance(models.ProvenanceNone), actualRule.GrafanaManagedAlert.Provenance)
					}
				}
			}
		}
		require.True(t, found)
	})
	t.Run("should enforce order of rules in the group", func(t *testing.T) {
		orgID := rand.Int63()
		folder := randFolder()
		ruleStore := fakes.NewRuleStore(t)
		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
		groupKey := models.GenerateGroupKey(orgID)
		groupKey.NamespaceUID = folder.UID

		expectedRules := make([]*models.AlertRule, 0)
		for i := 0; i < 10; i++ {
			expectedRules = append(expectedRules, gen.With(gen.WithGroupKey(groupKey), gen.WithUniqueGroupIndex(), gen.WithUpdatedBy(util.Pointer(models.UserUID(util.GenerateShortUID())))).GenerateManyRef(5, 10)...)
		}
		ruleStore.PutRule(context.Background(), expectedRules...)

		perms := createPermissionsForRules(expectedRules, orgID)
		req := createRequestContextWithPerms(orgID, perms, nil)

		fakeUserService := usertest.NewUserServiceFake()
		userUids := make([]string, 0)
		for _, rule := range expectedRules {
			if rule.UpdatedBy != nil {
				userUids = append(userUids, string(*rule.UpdatedBy))
			}
		}
		fakeUserServiceResponse := []*user.User{}
		for i, uid := range userUids {
			fakeUserServiceResponse = append(fakeUserServiceResponse, &user.User{ID: int64(i + 1), UID: uid})
		}
		fakeUserService.ExpectedListUsersByIdOrUid = fakeUserServiceResponse

		svc := createService(ruleStore, fakeUserService)
		response := svc.RouteGetNamespaceRulesConfig(req, folder.UID)

		require.Equal(t, http.StatusAccepted, response.Status())
		require.Equal(t, 1, len(fakeUserService.ListUsersByIdOrUidCalls)) // only one call to the user service
		result := &apimodels.NamespaceConfigResponse{}
		require.NoError(t, json.Unmarshal(response.Body(), result))
		require.NotNil(t, result)

		models.RulesGroup(expectedRules).SortByGroupIndex()

		groups, ok := (*result)[folder.Fullpath]
		require.True(t, ok)
		require.Len(t, groups, 1)
		group := groups[0]
		require.Equal(t, groupKey.RuleGroup, group.Name)
		for i, actual := range groups[0].Rules {
			expected := expectedRules[i]
			if actual.GrafanaManagedAlert.UID != expected.UID {
				var actualUIDs []string
				var expectedUIDs []string
				for _, rule := range group.Rules {
					actualUIDs = append(actualUIDs, rule.GrafanaManagedAlert.UID)
				}
				for _, rule := range expectedRules {
					expectedUIDs = append(expectedUIDs, rule.UID)
				}
				require.Fail(t, fmt.Sprintf("rules are not sorted by group index. Expected: %v. Actual: %v", expectedUIDs, actualUIDs))
			}
		}
	})
}

func TestRouteGetRuleByUID(t *testing.T) {
	t.Run("rule is successfully fetched with the correct UID", func(t *testing.T) {
		orgID := rand.Int63()
		folder := randFolder()
		ruleStore := fakes.NewRuleStore(t)
		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
		groupKey := models.GenerateGroupKey(orgID)
		groupKey.NamespaceUID = folder.UID
		gen := models.RuleGen.With(models.RuleGen.WithGroupKey(groupKey))

		createdRules := gen.With(
			gen.WithUniqueGroupIndex(), gen.WithUniqueID(),
			gen.WithEditorSettingsSimplifiedQueryAndExpressionsSection(true),
			gen.WithEditorSettingsSimplifiedNotificationsSection(true),
			gen.WithKeepFiringFor(30*time.Second),
		).GenerateManyRef(3)
		require.Len(t, createdRules, 3)
		ruleStore.PutRule(context.Background(), createdRules...)

		perms := createPermissionsForRules(createdRules, orgID)
		req := createRequestContextWithPerms(orgID, perms, nil)

		expectedRule := createdRules[1]
		fakeUserService := usertest.NewUserServiceFake()
		svc := createService(ruleStore, fakeUserService)
		response := svc.RouteGetRuleByUID(req, expectedRule.UID)

		require.Equal(t, http.StatusOK, response.Status())
		result := &apimodels.GettableExtendedRuleNode{}
		require.NoError(t, json.Unmarshal(response.Body(), result))
		require.NotNil(t, result)

		require.Equal(t, expectedRule.UID, result.GrafanaManagedAlert.UID)
		require.Equal(t, expectedRule.RuleGroup, result.GrafanaManagedAlert.RuleGroup)
		require.Equal(t, expectedRule.Title, result.GrafanaManagedAlert.Title)
		require.Equal(t, int64(expectedRule.KeepFiringFor), int64(*(result.KeepFiringFor)))
		require.True(t, result.GrafanaManagedAlert.Metadata.EditorSettings.SimplifiedQueryAndExpressionsSection)
		require.True(t, result.GrafanaManagedAlert.Metadata.EditorSettings.SimplifiedNotificationsSection)

		t.Run("should resolve Updated_by with user service", func(t *testing.T) {
			testcases := []struct {
				desc             string
				UpdatedBy        *models.UserUID
				User             *user.User
				UserServiceError error
				UserServiceCalls []usertest.ListUsersByIdOrUidCall
				Expected         *apimodels.UserInfo
			}{
				{
					desc:             "nil if UpdatedBy is nil",
					UpdatedBy:        nil,
					User:             nil,
					UserServiceError: nil,
					UserServiceCalls: nil,
					Expected:         nil,
				},
				{
					desc:             "just UID if user is not found",
					UpdatedBy:        util.Pointer(models.UserUID("test-uid")),
					User:             nil,
					UserServiceError: nil,
					UserServiceCalls: []usertest.ListUsersByIdOrUidCall{{Uids: []string{"test-uid"}, Ids: []int64{}}},
					Expected: &apimodels.UserInfo{
						UID: "test-uid",
					},
				},
				{
					desc:             "just UID if error",
					UpdatedBy:        util.Pointer(models.UserUID("test-uid")),
					UserServiceError: errors.New("error"),
					UserServiceCalls: []usertest.ListUsersByIdOrUidCall{{Uids: []string{"test-uid"}, Ids: []int64{}}},
					Expected: &apimodels.UserInfo{
						UID: "test-uid",
					},
				},
				{
					desc:      "login if it's known user",
					UpdatedBy: util.Pointer(models.UserUID("test-uid")),
					User: &user.User{
						UID:   "test-uid",
						Login: "Test",
					},
					UserServiceError: nil,
					UserServiceCalls: []usertest.ListUsersByIdOrUidCall{{Uids: []string{"test-uid"}, Ids: []int64{}}},
					Expected: &apimodels.UserInfo{
						UID:  "test-uid",
						Name: "Test",
					},
				},
				{
					desc:             "recognize system identifier (alerting)",
					UpdatedBy:        &models.AlertingUserUID,
					User:             nil,
					UserServiceError: nil,
					UserServiceCalls: nil,
					Expected: &apimodels.UserInfo{
						UID: string(models.AlertingUserUID),
					},
				},
				{
					desc:             "recognize system identifier (provisioning)",
					UpdatedBy:        &models.FileProvisioningUserUID,
					User:             nil,
					UserServiceError: nil,
					UserServiceCalls: nil,
					Expected: &apimodels.UserInfo{
						UID: string(models.FileProvisioningUserUID),
					},
				},
			}
			for _, tc := range testcases {
				t.Run(tc.desc, func(t *testing.T) {
					expectedRule.UpdatedBy = tc.UpdatedBy
					usvc := usertest.NewUserServiceFake()
					if tc.User != nil {
						usvc.ExpectedListUsersByIdOrUid = []*user.User{tc.User}
					}
					usvc.ExpectedError = tc.UserServiceError
					svc := createService(ruleStore, usvc)

					response := svc.RouteGetRuleByUID(req, expectedRule.UID)

					require.Equal(t, http.StatusOK, response.Status())
					result := &apimodels.GettableExtendedRuleNode{}
					require.NoError(t, json.Unmarshal(response.Body(), result))
					require.NotNil(t, result)
					require.Equal(t, tc.UserServiceCalls, usvc.ListUsersByIdOrUidCalls)

					require.Equal(t, tc.Expected, result.GrafanaManagedAlert.UpdatedBy)
				})
			}
		})
	})

	t.Run("error when fetching rule with non-existent UID", func(t *testing.T) {
		orgID := rand.Int63()
		folder := randFolder()
		ruleStore := fakes.NewRuleStore(t)
		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
		groupKey := models.GenerateGroupKey(orgID)
		groupKey.NamespaceUID = folder.UID
		gen := models.RuleGen.With(models.RuleGen.WithGroupKey(groupKey))

		createdRules := gen.With(gen.WithUniqueGroupIndex(), gen.WithUniqueID()).GenerateManyRef(3)
		require.Len(t, createdRules, 3)
		ruleStore.PutRule(context.Background(), createdRules...)

		perms := createPermissionsForRules(createdRules, orgID)
		req := createRequestContextWithPerms(orgID, perms, nil)
		fakeUserService := usertest.NewUserServiceFake()
		svc := createService(ruleStore, fakeUserService)
		response := svc.RouteGetRuleByUID(req, "foobar")

		require.Equal(t, http.StatusNotFound, response.Status())
		require.Equal(t, 0, len(fakeUserService.ListUsersByIdOrUidCalls))
	})
}

func TestRouteGetRuleVersionsByUID(t *testing.T) {
	orgID := rand.Int63()
	f := randFolder()
	groupKey := models.GenerateGroupKey(orgID)
	groupKey.NamespaceUID = f.UID
	gen := models.RuleGen.With(models.RuleGen.WithGroupKey(groupKey), models.RuleGen.WithUniqueID())

	t.Run("rule history is successfully fetched with the correct UID", func(t *testing.T) {
		ruleStore := fakes.NewRuleStore(t)
		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], f)

		rule := gen.GenerateRef()
		historyRules := gen.With(gen.WithUID(rule.UID)).GenerateManyRef(3)
		history := make([]*models.AlertRuleVersion, len(historyRules))
		// simulate order of the history
		rule.ID = 100
		for i, alertRule := range historyRules {
			alertRule.ID = rule.ID - int64(i) - 1

			history[i] = &models.AlertRuleVersion{
				AlertRule: *alertRule,
				Message:   fmt.Sprintf("revision %d", i),
			}
		}

		ruleStore.PutRule(context.Background(), rule)
		ruleStore.History[rule.GUID] = append(ruleStore.History[rule.GUID], history...)

		perms := createPermissionsForRules([]*models.AlertRule{rule}, orgID)
		req := createRequestContextWithPerms(orgID, perms, nil)

		svc := createService(ruleStore, nil)
		response := svc.RouteGetRuleVersionsByUID(req, rule.UID)

		require.Equal(t, http.StatusOK, response.Status())
		var result apimodels.GettableRuleVersions
		require.NoError(t, json.Unmarshal(response.Body(), &result))
		require.NotNil(t, result)

		require.Len(t, result, len(history)+1) // history + current version

		t.Run("should be in correct order", func(t *testing.T) {
			expectedHistory := append([]*models.AlertRuleVersion{{AlertRule: *rule}}, history...)
			for i, rul := range expectedHistory {
				assert.Equal(t, rul.UID, result[i].GrafanaManagedAlert.UID)
			}
		})

		t.Run("should have correct messages", func(t *testing.T) {
			expectedMessages := make([]string, 0, len(history)+1)
			expectedMessages = append(expectedMessages, "")
			for i := range history {
				expectedMessages = append(expectedMessages, history[i].Message)
			}
			for i := range expectedMessages {
				assert.Equal(t, expectedMessages[i], result[i].GrafanaManagedAlert.Message)
			}
		})
	})

	t.Run("NotFound when rule does not exist", func(t *testing.T) {
		ruleStore := fakes.NewRuleStore(t)
		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], f)
		ruleKey := models.AlertRuleKey{
			OrgID: orgID,
			UID:   "test",
		}
		guid := uuid.NewString()
		historyRules := gen.With(gen.WithGUID(guid), gen.WithKey(ruleKey)).GenerateManyRef(3)
		history := make([]*models.AlertRuleVersion, len(historyRules))
		for i, alertRule := range historyRules {
			history[i] = &models.AlertRuleVersion{
				AlertRule: *alertRule,
				Message:   fmt.Sprintf("revision %d", i),
			}
		}
		ruleStore.History[guid] = append(ruleStore.History[guid], history...) // even if history is full of records

		perms := createPermissionsForRules(historyRules, orgID)
		req := createRequestContextWithPerms(orgID, perms, nil)
		response := createService(ruleStore, nil).RouteGetRuleVersionsByUID(req, ruleKey.UID)

		require.Equal(t, http.StatusNotFound, response.Status())
	})

	t.Run("Empty result when rule history is empty", func(t *testing.T) {
		ruleStore := fakes.NewRuleStore(t)
		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], f)
		ruleKey := models.AlertRuleKey{
			OrgID: orgID,
			UID:   "test",
		}
		guid := uuid.NewString()
		rule := gen.With(gen.WithKey(ruleKey), gen.WithGUID(guid)).GenerateRef()
		ruleStore.PutRule(context.Background(), rule)
		ruleStore.History[guid] = nil

		perms := createPermissionsForRules([]*models.AlertRule{rule}, orgID)
		req := createRequestContextWithPerms(orgID, perms, nil)
		response := createService(ruleStore, nil).RouteGetRuleVersionsByUID(req, ruleKey.UID)

		require.Equal(t, http.StatusOK, response.Status())

		var result apimodels.GettableRuleVersions
		require.NoError(t, json.Unmarshal(response.Body(), &result))
		require.Empty(t, result)
	})

	t.Run("Unauthorized if user does not have access to the current rule", func(t *testing.T) {
		ruleStore := fakes.NewRuleStore(t)
		anotherFolder := randFolder()
		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], f, anotherFolder)
		ruleKey := models.AlertRuleKey{
			OrgID: orgID,
			UID:   "test",
		}
		guid := uuid.NewString()
		rule := gen.With(gen.WithGUID(guid), gen.WithKey(ruleKey), gen.WithNamespaceUID(anotherFolder.UID)).GenerateRef()
		ruleStore.PutRule(context.Background(), rule)
		historyRules := gen.With(gen.WithGUID(guid), gen.WithKey(ruleKey)).GenerateManyRef(3)
		history := make([]*models.AlertRuleVersion, len(historyRules))
		for i, alertRule := range historyRules {
			history[i] = &models.AlertRuleVersion{
				AlertRule: *alertRule,
				Message:   fmt.Sprintf("revision %d", i),
			}
		}
		ruleStore.History[guid] = history

		perms := createPermissionsForRules(historyRules, orgID) // grant permissions to all records in history but not the rule itself
		req := createRequestContextWithPerms(orgID, perms, nil)
		response := createService(ruleStore, nil).RouteGetRuleVersionsByUID(req, ruleKey.UID)

		require.Equal(t, http.StatusForbidden, response.Status())
	})
}

func TestRouteGetRulesConfig(t *testing.T) {
	gen := models.RuleGen
	t.Run("fine-grained access is enabled", func(t *testing.T) {
		t.Run("should check access to data source", func(t *testing.T) {
			orgID := rand.Int63()
			ruleStore := fakes.NewRuleStore(t)
			folder1 := randFolder()
			folder2 := randFolder()
			ruleStore.Folders[orgID] = []*folder.Folder{folder1, folder2}

			group1Key := models.GenerateGroupKey(orgID)
			group1Key.NamespaceUID = folder1.UID
			group2Key := models.GenerateGroupKey(orgID)
			group2Key.NamespaceUID = folder2.UID

			ruleUpdatedBy := util.Pointer(models.UserUID(util.GenerateShortUID()))

			group1 := gen.With(gen.WithGroupKey(group1Key), gen.WithUpdatedBy(ruleUpdatedBy)).GenerateManyRef(2, 6)
			group2 := gen.With(gen.WithGroupKey(group2Key), gen.WithUpdatedBy(ruleUpdatedBy)).GenerateManyRef(2, 6)
			ruleStore.PutRule(context.Background(), append(group1, group2...)...)

			t.Run("and do not return group if user does not have access to one of rules", func(t *testing.T) {
				permissions := createPermissionsForRules(append(group1, group2[1:]...), orgID)
				request := createRequestContextWithPerms(orgID, permissions, nil)

				fakeUserService := usertest.NewUserServiceFake()
				svc := createService(ruleStore, fakeUserService)
				response := svc.RouteGetRulesConfig(request)
				require.Equal(t, http.StatusOK, response.Status())

				require.Equal(t, 1, len(fakeUserService.ListUsersByIdOrUidCalls)) // only one call to the user service

				result := &apimodels.NamespaceConfigResponse{}
				require.NoError(t, json.Unmarshal(response.Body(), result))
				require.NotNil(t, result)

				require.Contains(t, *result, folder1.Fullpath)
				require.NotContains(t, *result, folder2.UID)

				groups := (*result)[folder1.Fullpath]
				require.Len(t, groups, 1)
				require.Equal(t, group1Key.RuleGroup, groups[0].Name)
				require.Len(t, groups[0].Rules, len(group1))
			})
		})
	})

	t.Run("should return rules in group sorted by group index", func(t *testing.T) {
		orgID := rand.Int63()
		folder := randFolder()
		ruleStore := fakes.NewRuleStore(t)
		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
		groupKey := models.GenerateGroupKey(orgID)
		groupKey.NamespaceUID = folder.UID

		ruleUpdatedBy := util.Pointer(models.UserUID(util.GenerateShortUID()))

		expectedRules := gen.With(gen.WithGroupKey(groupKey), gen.WithUniqueGroupIndex(), gen.WithUpdatedBy(ruleUpdatedBy)).GenerateManyRef(5, 10)
		ruleStore.PutRule(context.Background(), expectedRules...)

		perms := createPermissionsForRules(expectedRules, orgID)
		req := createRequestContextWithPerms(orgID, perms, nil)
		fakeUserService := usertest.NewUserServiceFake()
		svc := createService(ruleStore, fakeUserService)
		response := svc.RouteGetRulesConfig(req)

		require.Equal(t, http.StatusOK, response.Status())
		result := &apimodels.NamespaceConfigResponse{}
		require.NoError(t, json.Unmarshal(response.Body(), result))
		require.NotNil(t, result)
		require.Equal(t, 1, len(fakeUserService.ListUsersByIdOrUidCalls)) // only one call to the user service

		models.RulesGroup(expectedRules).SortByGroupIndex()

		groups, ok := (*result)[folder.Fullpath]
		require.True(t, ok)
		require.Len(t, groups, 1)
		group := groups[0]
		require.Equal(t, groupKey.RuleGroup, group.Name)
		for i, actual := range groups[0].Rules {
			expected := expectedRules[i]
			if actual.GrafanaManagedAlert.UID != expected.UID {
				var actualUIDs []string
				var expectedUIDs []string
				for _, rule := range group.Rules {
					actualUIDs = append(actualUIDs, rule.GrafanaManagedAlert.UID)
				}
				for _, rule := range expectedRules {
					expectedUIDs = append(expectedUIDs, rule.UID)
				}
				require.Fail(t, fmt.Sprintf("rules are not sorted by group index. Expected: %v. Actual: %v", expectedUIDs, actualUIDs))
			}
		}
	})
}

func TestRouteGetRulesGroupConfig(t *testing.T) {
	gen := models.RuleGen
	t.Run("should return rules in group sorted by group index", func(t *testing.T) {
		orgID := rand.Int63()
		folder := randFolder()
		ruleStore := fakes.NewRuleStore(t)
		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
		groupKey := models.GenerateGroupKey(orgID)
		groupKey.NamespaceUID = folder.UID

		ruleUpdatedBy := util.Pointer(models.UserUID(util.GenerateShortUID()))

		expectedRules := gen.With(gen.WithGroupKey(groupKey), gen.WithUniqueGroupIndex(), gen.WithUpdatedBy(ruleUpdatedBy)).GenerateManyRef(5, 10)
		ruleStore.PutRule(context.Background(), expectedRules...)

		perms := createPermissionsForRules(expectedRules, orgID)
		req := createRequestContextWithPerms(orgID, perms, nil)

		fakeUserService := usertest.NewUserServiceFake()
		svc := createService(ruleStore, fakeUserService)
		response := svc.RouteGetRulesGroupConfig(req, folder.UID, groupKey.RuleGroup)

		require.Equal(t, http.StatusAccepted, response.Status())
		result := &apimodels.RuleGroupConfigResponse{}
		require.NoError(t, json.Unmarshal(response.Body(), result))
		require.NotNil(t, result)
		require.Equal(t, 1, len(fakeUserService.ListUsersByIdOrUidCalls)) // only one call to the user service

		models.RulesGroup(expectedRules).SortByGroupIndex()

		for i, actual := range result.Rules {
			expected := expectedRules[i]
			if actual.GrafanaManagedAlert.UID != expected.UID {
				var actualUIDs []string
				var expectedUIDs []string
				for _, rule := range result.Rules {
					actualUIDs = append(actualUIDs, rule.GrafanaManagedAlert.UID)
				}
				for _, rule := range expectedRules {
					expectedUIDs = append(expectedUIDs, rule.UID)
				}
				require.Fail(t, fmt.Sprintf("rules are not sorted by group index. Expected: %v. Actual: %v", expectedUIDs, actualUIDs))
			}
		}
	})
	t.Run("should return a 404 when fetching a group that doesn't exist", func(t *testing.T) {
		orgID := rand.Int63()
		folder := randFolder()
		ruleStore := fakes.NewRuleStore(t)
		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
		groupKey := models.GenerateGroupKey(orgID)
		groupKey.NamespaceUID = folder.UID

		expectedRules := gen.With(gen.WithGroupKey(groupKey), gen.WithUniqueGroupIndex()).GenerateManyRef(5, 10)
		ruleStore.PutRule(context.Background(), expectedRules...)

		perms := createPermissionsForRules(expectedRules, orgID)
		req := createRequestContextWithPerms(orgID, perms, nil)

		fakeUserService := usertest.NewUserServiceFake()
		svc := createService(ruleStore, fakeUserService)

		response := svc.RouteGetRulesGroupConfig(req, folder.UID, "non-existent-rule-group")

		require.Equal(t, http.StatusNotFound, response.Status())
		require.Equal(t, 0, len(fakeUserService.ListUsersByIdOrUidCalls))
	})
}

func TestVerifyProvisionedRulesNotAffected(t *testing.T) {
	orgID := rand.Int63()
	group := models.GenerateGroupKey(orgID)
	affectedGroups := make(map[models.AlertRuleGroupKey]models.RulesGroup)
	gen := models.RuleGen
	var allRules []*models.AlertRule
	{
		rules := gen.With(gen.WithGroupKey(group)).GenerateManyRef(1, 4)
		allRules = append(allRules, rules...)
		affectedGroups[group] = rules
		for i := 0; i < rand.Intn(3)+1; i++ {
			g := models.GenerateGroupKey(orgID)
			rules := gen.With(gen.WithGroupKey(g)).GenerateManyRef(1, 4)
			allRules = append(allRules, rules...)
			affectedGroups[g] = rules
		}
	}
	ch := &store.GroupDelta{
		GroupKey:       group,
		AffectedGroups: affectedGroups,
	}

	t.Run("should return error if at least one rule in affected groups is provisioned", func(t *testing.T) {
		rand.Shuffle(len(allRules), func(i, j int) {
			allRules[j], allRules[i] = allRules[i], allRules[j]
		})
		storeResult := make(map[string]models.Provenance, len(allRules))
		storeResult[allRules[0].UID] = models.ProvenanceAPI
		storeResult[allRules[1].UID] = models.ProvenanceFile

		provenanceStore := &provisioning.MockProvisioningStore{}
		provenanceStore.EXPECT().GetProvenances(mock.Anything, orgID, "alertRule").Return(storeResult, nil)

		result := verifyProvisionedRulesNotAffected(context.Background(), provenanceStore, orgID, ch)
		require.Error(t, result)
		require.ErrorIs(t, result, errProvisionedResource)
		assert.Contains(t, result.Error(), allRules[0].GetGroupKey().String())
		assert.Contains(t, result.Error(), allRules[1].GetGroupKey().String())
	})

	t.Run("should return nil if all have ProvenanceNone", func(t *testing.T) {
		storeResult := make(map[string]models.Provenance, len(allRules))
		for _, rule := range allRules {
			storeResult[rule.UID] = models.ProvenanceNone
		}

		provenanceStore := &provisioning.MockProvisioningStore{}
		provenanceStore.EXPECT().GetProvenances(mock.Anything, orgID, "alertRule").Return(storeResult, nil)

		result := verifyProvisionedRulesNotAffected(context.Background(), provenanceStore, orgID, ch)
		require.NoError(t, result)
	})

	t.Run("should return nil if no alerts have provisioning status", func(t *testing.T) {
		provenanceStore := &provisioning.MockProvisioningStore{}
		provenanceStore.EXPECT().GetProvenances(mock.Anything, orgID, "alertRule").Return(make(map[string]models.Provenance, len(allRules)), nil)

		result := verifyProvisionedRulesNotAffected(context.Background(), provenanceStore, orgID, ch)
		require.NoError(t, result)
	})
}

func TestValidateQueries(t *testing.T) {
	gen := models.RuleGen
	delta := store.GroupDelta{
		New: []*models.AlertRule{
			gen.With(gen.WithCondition("New")).GenerateRef(),
		},
		Update: []store.RuleDelta{
			{
				Existing: gen.With(gen.WithCondition("New")).GenerateRef(),
				New:      gen.With(gen.WithCondition("Update_New")).GenerateRef(),
				Diff: cmputil.DiffReport{
					cmputil.Diff{
						Path: "SomeField",
					},
				},
			},
			{
				Existing: gen.With(gen.WithCondition("Update_Index_Existing")).GenerateRef(),
				New:      gen.With(gen.WithCondition("Update_Index_New")).GenerateRef(),
				Diff: cmputil.DiffReport{
					cmputil.Diff{
						Path: "RuleGroupIndex",
					},
				},
			},
		},
		Delete: gen.With(gen.WithCondition("Deleted")).GenerateManyRef(1),
	}

	t.Run("should not validate deleted rules or updated rules with ignored fields", func(t *testing.T) {
		validator := &recordingConditionValidator{}
		err := validateQueries(context.Background(), &delta, validator, nil)
		require.NoError(t, err)
		noValidate := []string{"Deleted", "Update_Index_New"}
		for _, condition := range validator.recorded {
			if !slices.Contains(noValidate, condition.Condition) {
				continue
			}
			assert.Failf(t, "validated unexpected condition", "condition '%s' was validated but should not", condition.Condition)
		}
	})
	t.Run("should return rule validate error if fails on new rule", func(t *testing.T) {
		validator := &recordingConditionValidator{
			hook: func(c models.Condition) error {
				if c.Condition == "New" {
					return errors.New("test")
				}
				return nil
			},
		}
		err := validateQueries(context.Background(), &delta, validator, nil)
		require.Error(t, err)
		require.ErrorIs(t, err, models.ErrAlertRuleFailedValidation)
	})
	t.Run("should return rule validate error with UID if fails on updated rule", func(t *testing.T) {
		validator := &recordingConditionValidator{
			hook: func(c models.Condition) error {
				if c.Condition == "Update_New" {
					return errors.New("test")
				}
				return nil
			},
		}
		err := validateQueries(context.Background(), &delta, validator, nil)
		require.Error(t, err)
		require.ErrorIs(t, err, models.ErrAlertRuleFailedValidation)
		require.ErrorContains(t, err, delta.Update[0].New.UID)
	})
}

func createServiceWithProvenanceStore(store *fakes.RuleStore, provenanceStore provisioning.ProvisioningStore) *RulerSrv {
	svc := createService(store, nil)
	svc.provenanceStore = provenanceStore
	return svc
}

func createService(store *fakes.RuleStore, _userService *usertest.FakeUserService) *RulerSrv {
	userService := _userService
	if _userService == nil {
		userService = usertest.NewUserServiceFake()
	}
	return &RulerSrv{
		xactManager:     store,
		store:           store,
		QuotaService:    nil,
		provenanceStore: fakes.NewFakeProvisioningStore(),
		log:             log.New("test"),
		cfg: &setting.UnifiedAlertingSettings{
			BaseInterval: 10 * time.Second,
		},
		authz:              accesscontrol.NewRuleService(acimpl.ProvideAccessControl(featuremgmt.WithFeatures())),
		amConfigStore:      &fakeAMRefresher{},
		amRefresher:        &fakeAMRefresher{},
		userService:        userService,
		conditionValidator: &recordingConditionValidator{},
	}
}

type fakeAMRefresher struct {
}

func (f *fakeAMRefresher) ApplyConfig(ctx context.Context, orgId int64, dbConfig *models.AlertConfiguration) error {
	return nil
}

func (f *fakeAMRefresher) GetLatestAlertmanagerConfiguration(ctx context.Context, orgID int64) (*models.AlertConfiguration, error) {
	return nil, nil
}

func createRequestContext(orgID int64, params map[string]string) *contextmodel.ReqContext {
	defaultPerms := map[int64]map[string][]string{orgID: {datasources.ActionQuery: []string{datasources.ScopeAll}}}
	return createRequestContextWithPerms(orgID, defaultPerms, params)
}

func createRequestContextWithPerms(orgID int64, permissions map[int64]map[string][]string, params map[string]string) *contextmodel.ReqContext {
	uri, _ := url.Parse("http://localhost")
	ctx := web.Context{
		Req: &http.Request{
			URL:    uri,
			Header: make(http.Header),
			Form:   make(url.Values),
		},
		Resp: web.NewResponseWriter("GET", httptest.NewRecorder()),
	}
	if params != nil {
		ctx.Req = web.SetURLParams(ctx.Req, params)
	}

	return &contextmodel.ReqContext{
		IsSignedIn: true,
		SignedInUser: &user.SignedInUser{
			Permissions: permissions,
			OrgID:       orgID,
		},
		Context: &ctx,
	}
}

func createPermissionsForRules(rules []*models.AlertRule, orgID int64) map[int64]map[string][]string {
	ns := map[string]any{}
	permissions := map[string][]string{}
	for _, rule := range rules {
		if _, ok := ns[rule.NamespaceUID]; !ok {
			scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(rule.NamespaceUID)
			permissions[dashboards.ActionFoldersRead] = append(permissions[dashboards.ActionFoldersRead], scope)
			permissions[ac.ActionAlertingRuleRead] = append(permissions[ac.ActionAlertingRuleRead], scope)
			permissions[ac.ActionAlertingRuleUpdate] = append(permissions[ac.ActionAlertingRuleUpdate], scope)
			ns[rule.NamespaceUID] = struct{}{}
		}
		for _, query := range rule.Data {
			permissions[datasources.ActionQuery] = append(permissions[datasources.ActionQuery], datasources.ScopeProvider.GetResourceScopeUID(query.DatasourceUID))
		}
	}
	return map[int64]map[string][]string{orgID: permissions}
}

func createPermissionsForRulesWithoutDS(rules []*models.AlertRule, orgID int64) map[int64]map[string][]string {
	ns := map[string]any{}
	permissions := map[string][]string{}
	for _, rule := range rules {
		if _, ok := ns[rule.NamespaceUID]; !ok {
			scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(rule.NamespaceUID)
			permissions[dashboards.ActionFoldersRead] = append(permissions[dashboards.ActionFoldersRead], scope)
			permissions[ac.ActionAlertingRuleRead] = append(permissions[ac.ActionAlertingRuleRead], scope)
			ns[rule.NamespaceUID] = struct{}{}
		}
	}
	return map[int64]map[string][]string{orgID: permissions}
}

func TestRouteUpdateNamespaceRules(t *testing.T) {
	orgID := rand.Int63()
	folder := randFolder()
	gen := models.RuleGen.With(
		models.RuleGen.WithOrgID(orgID),
		models.RuleGen.WithNamespaceUID(folder.UID),
	)

	initFakeRuleStore := func(t *testing.T) *fakes.RuleStore {
		ruleStore := fakes.NewRuleStore(t)
		ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
		return ruleStore
	}

	getRecordedUpdatedRules := func(ruleStore *fakes.RuleStore) []models.UpdateRule {
		raw := ruleStore.GetRecordedCommands(func(cmd any) (any, bool) {
			if u, ok := cmd.([]models.UpdateRule); ok {
				return u, true
			}
			return nil, false
		})

		updates := []models.UpdateRule{}
		for _, cmd := range raw {
			updates = append(updates, cmd.([]models.UpdateRule)...)
		}
		return updates
	}

	t.Run("should pause all non-provisioned rules in namespace", func(t *testing.T) {
		ruleStore := initFakeRuleStore(t)
		provisioningStore := fakes.NewFakeProvisioningStore()

		// Create 3 types of rules: paused, provisioned paused, and unpaused
		pausedRules := gen.With(gen.WithGroupPrefix("paused-"), gen.WithIsPaused(true)).GenerateManyRef(2)

		unpausedRules := gen.With(gen.WithGroupPrefix("unpaused-"), gen.WithIsPaused(false)).GenerateManyRef(1)

		provisionedRules := gen.With(
			gen.WithGroupPrefix("provisioned-"),
			gen.WithIsPaused(false),
		).GenerateManyRef(3)
		for _, r := range provisionedRules {
			err := provisioningStore.SetProvenance(context.Background(), r, orgID, models.ProvenanceAPI)
			require.NoError(t, err)
		}

		ruleStore.PutRule(context.Background(), unpausedRules...)
		ruleStore.PutRule(context.Background(), provisionedRules...)
		ruleStore.PutRule(context.Background(), pausedRules...)

		allRules := append(append(unpausedRules, provisionedRules...), pausedRules...)
		permissions := createPermissionsForRules(allRules, orgID)
		requestCtx := createRequestContextWithPerms(orgID, permissions, nil)

		svc := createServiceWithProvenanceStore(ruleStore, provisioningStore)
		response := svc.RouteUpdateNamespaceRules(requestCtx, apimodels.UpdateNamespaceRulesRequest{
			IsPaused: util.Pointer(true),
		}, folder.UID)

		require.Equal(t, http.StatusAccepted, response.Status())
		result := &apimodels.UpdateNamespaceRulesResponse{}
		require.NoError(t, json.Unmarshal(response.Body(), result))
		require.Equal(t, "rules updated successfully", result.Message)

		updatedRules := getRecordedUpdatedRules(ruleStore)
		require.Len(t, updatedRules, len(unpausedRules))

		for _, update := range updatedRules {
			require.True(t, update.New.IsPaused)
		}
	})

	t.Run("should unpause all non-provisioned rules in namespace", func(t *testing.T) {
		ruleStore := initFakeRuleStore(t)
		provisioningStore := fakes.NewFakeProvisioningStore()

		// Create 3 types of rules: paused, provisioned paused, and unpaused
		pausedRules := gen.With(gen.WithGroupPrefix("paused-"), gen.WithIsPaused(true)).GenerateManyRef(4)

		unpausedRules := gen.With(gen.WithGroupPrefix("unpaused-"), gen.WithIsPaused(false)).GenerateManyRef(3)

		provisionedRules := gen.With(
			gen.WithGroupPrefix("provisioned-"),
			gen.WithIsPaused(false),
		).GenerateManyRef(2)
		for _, r := range provisionedRules {
			err := provisioningStore.SetProvenance(context.Background(), r, orgID, models.ProvenanceAPI)
			require.NoError(t, err)
		}

		ruleStore.PutRule(context.Background(), pausedRules...)
		ruleStore.PutRule(context.Background(), provisionedRules...)
		ruleStore.PutRule(context.Background(), unpausedRules...)

		allRules := append(append(pausedRules, provisionedRules...), unpausedRules...)
		permissions := createPermissionsForRules(allRules, orgID)
		requestCtx := createRequestContextWithPerms(orgID, permissions, nil)

		svc := createServiceWithProvenanceStore(ruleStore, provisioningStore)
		response := svc.RouteUpdateNamespaceRules(requestCtx, apimodels.UpdateNamespaceRulesRequest{
			IsPaused: util.Pointer(false),
		}, folder.UID)

		require.Equal(t, http.StatusAccepted, response.Status())
		result := &apimodels.UpdateNamespaceRulesResponse{}
		require.NoError(t, json.Unmarshal(response.Body(), result))
		require.Equal(t, "rules updated successfully", result.Message)

		updatedRules := getRecordedUpdatedRules(ruleStore)
		require.Len(t, updatedRules, len(pausedRules))

		// all rules are now unpaused
		for _, update := range updatedRules {
			require.False(t, update.New.IsPaused)
		}
	})

	t.Run("returns 202 when no rules need updating", func(t *testing.T) {
		ruleStore := initFakeRuleStore(t)
		provisioningStore := fakes.NewFakeProvisioningStore()

		// Create already unpaused rules
		rules := gen.With(gen.WithGroupPrefix("paused-"), gen.WithIsPaused(false)).GenerateManyRef(5)

		ruleStore.PutRule(context.Background(), rules...)

		permissions := createPermissionsForRules(rules, orgID)
		requestCtx := createRequestContextWithPerms(orgID, permissions, nil)

		// Create request to unpause rules (they are already unpaused)
		svc := createServiceWithProvenanceStore(ruleStore, provisioningStore)
		response := svc.RouteUpdateNamespaceRules(requestCtx, apimodels.UpdateNamespaceRulesRequest{
			IsPaused: util.Pointer(false),
		}, folder.UID)

		require.Equal(t, http.StatusAccepted, response.Status())
		result := &apimodels.UpdateNamespaceRulesResponse{}
		require.NoError(t, json.Unmarshal(response.Body(), result))
		require.Equal(t, "rules updated successfully", result.Message)

		// Verify no rules were updated
		updatedRules := getRecordedUpdatedRules(ruleStore)
		require.Empty(t, updatedRules)
	})

	t.Run("should return 202 with 'no rules to update in namespace' when namespace is empty", func(t *testing.T) {
		ruleStore := initFakeRuleStore(t)
		provisioningStore := fakes.NewFakeProvisioningStore()

		requestCtx := createRequestContextWithPerms(orgID, map[int64]map[string][]string{}, nil)

		svc := createServiceWithProvenanceStore(ruleStore, provisioningStore)
		response := svc.RouteUpdateNamespaceRules(requestCtx, apimodels.UpdateNamespaceRulesRequest{
			IsPaused: util.Pointer(true),
		}, folder.UID)

		require.Equal(t, http.StatusAccepted, response.Status())
		result := &apimodels.UpdateNamespaceRulesResponse{}
		require.NoError(t, json.Unmarshal(response.Body(), result))
		require.Equal(t, "no rules to update in namespace", result.Message)

		updatedRules := getRecordedUpdatedRules(ruleStore)
		require.Empty(t, updatedRules)
	})

	t.Run("should handle folder not found", func(t *testing.T) {
		ruleStore := initFakeRuleStore(t)
		provisioningStore := fakes.NewFakeProvisioningStore()

		requestCtx := createRequestContextWithPerms(orgID, map[int64]map[string][]string{}, nil)

		svc := createServiceWithProvenanceStore(ruleStore, provisioningStore)
		response := svc.RouteUpdateNamespaceRules(requestCtx, apimodels.UpdateNamespaceRulesRequest{
			IsPaused: util.Pointer(true),
		}, "non-existent-folder-uid")

		require.Equal(t, http.StatusNotFound, response.Status())

		updatedRules := getRecordedUpdatedRules(ruleStore)
		require.Empty(t, updatedRules)
	})

	t.Run("should return 202 with no updates when the user does not see any rules", func(t *testing.T) {
		ruleStore := initFakeRuleStore(t)
		provisioningStore := fakes.NewFakeProvisioningStore()

		rules := gen.GenerateManyRef(2)
		ruleStore.PutRule(context.Background(), rules...)

		permissions := map[int64]map[string][]string{orgID: {}}
		requestCtx := createRequestContextWithPerms(orgID, permissions, nil)

		svc := createServiceWithProvenanceStore(ruleStore, provisioningStore)
		response := svc.RouteUpdateNamespaceRules(requestCtx, apimodels.UpdateNamespaceRulesRequest{
			IsPaused: util.Pointer(true),
		}, folder.UID)

		require.Equal(t, http.StatusAccepted, response.Status())
		result := &apimodels.UpdateNamespaceRulesResponse{}
		require.NoError(t, json.Unmarshal(response.Body(), result))
		require.Equal(t, "no rules to update in namespace", result.Message)

		updatedRules := getRecordedUpdatedRules(ruleStore)
		require.Empty(t, updatedRules)
	})
}
