package notifier

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"testing"

	"github.com/grafana/alerting/receivers/line"
	"github.com/prometheus/alertmanager/config"
	"github.com/prometheus/alertmanager/pkg/labels"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/grafana/grafana/pkg/apimachinery/identity"
	"github.com/grafana/grafana/pkg/infra/db"
	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/infra/tracing"
	"github.com/grafana/grafana/pkg/services/accesscontrol"
	"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
	"github.com/grafana/grafana/pkg/services/featuremgmt"
	ac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
	"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/notifier/legacy_storage"
	"github.com/grafana/grafana/pkg/services/ngalert/provisioning/validation"
	"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
	"github.com/grafana/grafana/pkg/services/org"
	"github.com/grafana/grafana/pkg/services/secrets"
	"github.com/grafana/grafana/pkg/services/secrets/database"
	fake_secrets "github.com/grafana/grafana/pkg/services/secrets/fakes"
	"github.com/grafana/grafana/pkg/services/secrets/manager"
	"github.com/grafana/grafana/pkg/services/user"
	"github.com/grafana/grafana/pkg/util"
	"github.com/grafana/grafana/pkg/util/testutil"
)

func TestIntegrationReceiverService_GetReceiver(t *testing.T) {
	testutil.SkipIntegrationTestInShortMode(t)

	sqlStore := db.InitTestDB(t)
	secretsService := manager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))

	redactedUser := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
		1: {
			accesscontrol.ActionAlertingNotificationsRead: nil,
		},
	}}

	t.Run("service gets receiver from AM config", func(t *testing.T) {
		sut := createReceiverServiceSut(t, secretsService)
		recv, err := sut.GetReceiver(context.Background(), singleQ(1, "slack receiver"), redactedUser)
		require.NoError(t, err)
		require.Equal(t, "slack receiver", recv.Name)
		require.Len(t, recv.Integrations, 1)
		require.Equal(t, "UID2", recv.Integrations[0].UID)
	})

	t.Run("service returns error when receiver does not exist", func(t *testing.T) {
		sut := createReceiverServiceSut(t, secretsService)

		_, err := sut.GetReceiver(context.Background(), singleQ(1, "receiver1"), redactedUser)
		require.ErrorIs(t, err, legacy_storage.ErrReceiverNotFound)
	})

	t.Run("when includeImported is true", func(t *testing.T) {
		t.Run("gets imported receivers", func(t *testing.T) {
			sut := createReceiverServiceSut(t, secretsService, withImportedIncluded)

			recv, err := sut.GetReceiver(context.Background(), singleQ(1, "receiver1"), redactedUser)
			require.NoError(t, err)
			assert.Equal(t, models.ResourceOriginImported, recv.Origin)
			assert.Equal(t, "receiver1", recv.Name)
			assert.Equal(t, models.ProvenanceConvertedPrometheus, recv.Provenance)

			require.Len(t, recv.Integrations, 2)
			integration := recv.Integrations[0]
			assert.Equal(t, "", integration.UID)
		})

		t.Run("falls to only Grafana if cannot read imported receivers", func(t *testing.T) {
			sut := createReceiverServiceSut(t, secretsService, withImportedIncluded, withInvalidExtraConfig)
			_, err := sut.GetReceiver(context.Background(), singleQ(1, "receiver1"), redactedUser)
			require.ErrorIs(t, err, legacy_storage.ErrReceiverNotFound)
			_, err = sut.GetReceiver(context.Background(), singleQ(1, "slack receiver"), redactedUser)
			require.NoError(t, err)
		})
	})
}

func TestIntegrationReceiverService_GetReceivers(t *testing.T) {
	testutil.SkipIntegrationTestInShortMode(t)

	sqlStore := db.InitTestDB(t)
	secretsService := manager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))

	redactedUser := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
		1: {
			accesscontrol.ActionAlertingNotificationsRead: nil,
		},
	}}

	t.Run("service gets receivers from AM config", func(t *testing.T) {
		sut := createReceiverServiceSut(t, secretsService)

		Receivers, err := sut.GetReceivers(context.Background(), multiQ(1), redactedUser)
		require.NoError(t, err)
		require.Len(t, Receivers, 2)
		require.Equal(t, "grafana-default-email", Receivers[0].Name)
		require.Equal(t, "slack receiver", Receivers[1].Name)
	})

	t.Run("service filters receivers by name", func(t *testing.T) {
		sut := createReceiverServiceSut(t, secretsService)

		Receivers, err := sut.GetReceivers(context.Background(), multiQ(1, "slack receiver"), redactedUser)
		require.NoError(t, err)
		require.Len(t, Receivers, 1)
		require.Equal(t, "slack receiver", Receivers[0].Name)
	})

	t.Run("when includeImported is true", func(t *testing.T) {
		t.Run("returns imported receivers in the list", func(t *testing.T) {
			sut := createReceiverServiceSut(t, secretsService, withImportedIncluded)

			recvs, err := sut.GetReceivers(context.Background(), multiQ(1), redactedUser)
			require.NoError(t, err)
			require.Len(t, recvs, 5)
			names := make([]string, 0, len(recvs))
			for _, recv := range recvs {
				names = append(names, recv.Name)
			}
			expectedNames := []string{"grafana-default-email", "slack receiver", "receiver1", "empty receiver", "email-receiver"}
			assert.ElementsMatch(t, expectedNames, names)
		})

		t.Run("falls to only Grafana if cannot read imported receivers", func(t *testing.T) {
			sut := createReceiverServiceSut(t, secretsService, withImportedIncluded, withInvalidExtraConfig)
			recvs, err := sut.GetReceivers(context.Background(), multiQ(1), redactedUser)
			require.NoError(t, err)
			require.Len(t, recvs, 2)
		})
	})
}

func TestIntegrationReceiverService_DecryptRedact(t *testing.T) {
	testutil.SkipIntegrationTestInShortMode(t)

	sqlStore := db.InitTestDB(t)
	secretsService := manager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))

	getMethods := []string{"single", "multi"}

	readUser := &user.SignedInUser{
		OrgID: 1,
		Permissions: map[int64]map[string][]string{
			1: {accesscontrol.ActionAlertingNotificationsRead: nil},
		},
	}

	secretUser := &user.SignedInUser{
		OrgID: 1,
		Permissions: map[int64]map[string][]string{
			1: {
				accesscontrol.ActionAlertingNotificationsRead:    nil,
				accesscontrol.ActionAlertingReceiversReadSecrets: {models.ScopeReceiversAll},
			},
		},
	}

	for _, tc := range []struct {
		name    string
		decrypt bool
		user    identity.Requester
		err     string
	}{
		{
			name:    "service redacts receivers by default",
			decrypt: false,
			user:    readUser,
			err:     "",
		},
		{
			name:    "service returns error when trying to decrypt without permission",
			decrypt: true,
			user:    readUser,
			err:     "[alerting.unauthorized] user is not authorized to read any decrypted receiver",
		},
		{
			name:    "service returns error if user is nil and decrypt is true",
			decrypt: true,
			user:    nil,
			err:     "[alerting.unauthorized] user is not authorized to read any decrypted receiver",
		},
		{
			name:    "service decrypts receivers with permission",
			decrypt: true,
			user:    secretUser,
			err:     "",
		},
	} {
		origin := []struct {
			origin                models.ResourceOrigin
			receiver              string
			opts                  []createReceiverServiceSutOpt
			secureSettingKey      string
			decryptedSettingValue string
		}{
			{
				origin:                models.ResourceOriginImported,
				secureSettingKey:      "auth_password",
				decryptedSettingValue: "another-secret-password",
				receiver:              "email-receiver",
				opts: []createReceiverServiceSutOpt{
					withImportedIncluded,
				},
			},
			{
				origin:                models.ResourceOriginGrafana,
				secureSettingKey:      "url",
				decryptedSettingValue: "secure url",
				receiver:              "slack receiver",
			},
		}

		for _, o := range origin {
			for _, method := range getMethods {
				t.Run(fmt.Sprintf("%s %s", tc.name, method), func(t *testing.T) {
					t.Run(fmt.Sprintf("%s %s (%s)", tc.name, method, o.origin), func(t *testing.T) {
						sut := createReceiverServiceSut(t, secretsService, o.opts...)

						var res *models.Receiver
						var err error
						if method == "single" {
							q := singleQ(1, o.receiver)
							q.Decrypt = tc.decrypt
							res, err = sut.GetReceiver(context.Background(), q, tc.user)
						} else {
							q := multiQ(1, o.receiver)
							q.Decrypt = tc.decrypt
							var multiRes []*models.Receiver
							multiRes, err = sut.GetReceivers(context.Background(), q, tc.user)
							if tc.err == "" {
								require.Len(t, multiRes, 1)
								res = multiRes[0]
							}
						}
						if tc.err != "" {
							require.ErrorContains(t, err, tc.err)
							return
						}

						require.Equal(t, o.receiver, res.Name)
						require.Len(t, res.Integrations, 1)
						require.NoError(t, err)

						if tc.decrypt {
							require.Equal(t, o.decryptedSettingValue, res.Integrations[0].Settings[o.secureSettingKey])
							require.NotContains(t, res.Integrations[0].SecureSettings, o.secureSettingKey)
						} else {
							require.NotContains(t, res.Integrations[0].Settings, o.secureSettingKey)

							// Ensure the encrypted value exists and is not redacted or decrypted.
							require.NotEmpty(t, res.Integrations[0].SecureSettings[o.secureSettingKey])
							require.NotEqual(t, definitions.RedactedValue, res.Integrations[0].SecureSettings[o.secureSettingKey])
							require.NotEqual(t, o.decryptedSettingValue, res.Integrations[0].SecureSettings[o.secureSettingKey])
						}
					})
				})
			}
		}
	}
}

func TestReceiverService_Delete(t *testing.T) {
	secretsService := fake_secrets.NewFakeSecretsService()

	writer := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
		1: {
			accesscontrol.ActionAlertingNotificationsWrite: nil,
			accesscontrol.ActionAlertingNotificationsRead:  nil,
		},
	}}

	slackIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("slack"))()
	emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email"))()
	baseReceiver := models.ReceiverGen(models.ReceiverMuts.WithName("test receiver"), models.ReceiverMuts.WithIntegrations(slackIntegration))()

	for _, tc := range []struct {
		name             string
		user             identity.Requester
		deleteUID        string
		callerProvenance models.Provenance
		version          string
		storeSettings    map[models.AlertRuleKey][]models.NotificationSettings
		existing         *models.Receiver
		expectedErr      error
		opts             []createReceiverServiceSutOpt
	}{
		{
			name:      "service deletes receiver",
			user:      writer,
			deleteUID: baseReceiver.UID,
			existing:  util.Pointer(baseReceiver.Clone()),
		},
		{
			name:      "service deletes receiver with multiple integrations",
			user:      writer,
			deleteUID: baseReceiver.UID,
			existing:  util.Pointer(models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithIntegrations(slackIntegration, emailIntegration))),
		},
		{
			name:             "service deletes receiver with provenance",
			user:             writer,
			deleteUID:        baseReceiver.UID,
			callerProvenance: models.ProvenanceAPI,
			existing:         util.Pointer(models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithProvenance(models.ProvenanceAPI), models.ReceiverMuts.WithIntegrations(slackIntegration, emailIntegration))),
		},
		{
			name:      "non-existing receiver doesn't fail",
			user:      writer,
			deleteUID: "non-existent",
		},
		{
			name:        "delete receiver used by route fails",
			user:        writer,
			deleteUID:   legacy_storage.NameToUid("grafana-default-email"),
			version:     "cd95627c75892a39", // Correct version for grafana-default-email.
			expectedErr: makeReceiverInUseErr(true, nil),
		},
		{
			name:      "delete receiver used by rule fails",
			user:      writer,
			deleteUID: baseReceiver.UID,
			existing:  util.Pointer(baseReceiver.Clone()),
			storeSettings: map[models.AlertRuleKey][]models.NotificationSettings{
				{OrgID: 1, UID: "rule1"}: {
					models.NotificationSettingsGen(models.NSMuts.WithReceiver(baseReceiver.Name))(),
				},
			},
			expectedErr: makeReceiverInUseErr(false, []models.AlertRuleKey{{OrgID: 1, UID: "rule1"}}),
		},
		{
			name:             "delete provisioning provenance fails when caller is ProvenanceNone",
			user:             writer,
			deleteUID:        baseReceiver.UID,
			callerProvenance: models.ProvenanceNone,
			existing:         util.Pointer(models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithProvenance(models.ProvenanceFile))),
			expectedErr:      validation.MakeErrProvenanceChangeNotAllowed(models.ProvenanceFile, models.ProvenanceNone),
		},
		{
			name:             "delete provisioning provenance fails when caller is a different type", // TODO: This should fail once we move from lenient to strict validation.
			user:             writer,
			deleteUID:        baseReceiver.UID,
			callerProvenance: models.ProvenanceFile,
			existing:         util.Pointer(models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithProvenance(models.ProvenanceAPI))),
			// expectedErr:      validation.MakeErrProvenanceChangeNotAllowed(models.ProvenanceAPI, models.ProvenanceFile),
		},
		{
			name:        "delete receiver with optimistic version mismatch fails",
			user:        writer,
			deleteUID:   baseReceiver.UID,
			existing:    util.Pointer(baseReceiver.Clone()),
			version:     "wrong version",
			expectedErr: ErrReceiverVersionConflict,
		},
		{
			name:        "delete of receiver with non-Grafana origin fails",
			user:        writer,
			deleteUID:   legacy_storage.NameToUid("empty receiver"),
			expectedErr: ErrReceiverOrigin,
			opts: []createReceiverServiceSutOpt{
				withImportedIncluded,
			},
		},
		{
			name:      "delete of receiver succeeds even with invalid imported config",
			user:      writer,
			deleteUID: baseReceiver.UID,
			existing:  util.Pointer(baseReceiver.Clone()),
			opts: []createReceiverServiceSutOpt{
				withInvalidExtraConfig,
				withImportedIncluded,
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			store := &fakeAlertRuleNotificationStore{}
			store.ListNotificationSettingsFn = func(ctx context.Context, q models.ListNotificationSettingsQuery) (map[models.AlertRuleKey][]models.NotificationSettings, error) {
				return tc.storeSettings, nil
			}
			sut := createReceiverServiceSut(t, &secretsService, tc.opts...)
			sut.ruleNotificationsStore = store

			if tc.existing != nil {
				created, err := sut.CreateReceiver(context.Background(), tc.existing, tc.user.GetOrgID(), tc.user)
				require.NoError(t, err)

				if tc.version == "" {
					tc.version = created.Version
				}
			}

			err := sut.DeleteReceiver(context.Background(), tc.deleteUID, tc.callerProvenance, tc.version, tc.user.GetOrgID(), tc.user)
			if tc.expectedErr == nil {
				require.NoError(t, err)
			} else {
				assert.ErrorIs(t, err, tc.expectedErr)
				return
			}
			// Ensure receiver saved to store is correct.
			name, err := legacy_storage.UidToName(tc.deleteUID)
			require.NoError(t, err)
			q := models.GetReceiverQuery{OrgID: tc.user.GetOrgID(), Name: name}
			_, err = sut.GetReceiver(context.Background(), q, writer)
			assert.ErrorIs(t, err, legacy_storage.ErrReceiverNotFound)

			provenances, err := sut.provisioningStore.GetProvenances(context.Background(), tc.user.GetOrgID(), (&definitions.EmbeddedContactPoint{}).ResourceType())
			require.NoError(t, err)
			assert.Len(t, provenances, 0)
		})
	}
}

func TestReceiverService_Create(t *testing.T) {
	secretsService := fake_secrets.NewFakeSecretsService()

	writer := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
		1: {
			accesscontrol.ActionAlertingNotificationsWrite: nil,
			accesscontrol.ActionAlertingNotificationsRead:  nil,
		},
	}}
	decryptUser := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
		1: {
			accesscontrol.ActionAlertingReceiversReadSecrets: {models.ScopeReceiversAll},
		},
	}}

	// Used to mark generated fields to replace during test runtime.
	generated := func(n int) string { return fmt.Sprintf("[GENERATED]%d", n) }
	isGenerated := func(s string) bool { return strings.HasPrefix(s, "[GENERATED]") }

	slackIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("slack"))()
	emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email"))()
	lineIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig(line.Type))()
	baseReceiver := models.ReceiverGen(models.ReceiverMuts.WithName("test receiver"), models.ReceiverMuts.WithIntegrations(slackIntegration))()

	for _, tc := range []struct {
		name                string
		user                identity.Requester
		receiver            models.Receiver
		expectedCreate      models.Receiver
		expectedStored      *definitions.PostableApiReceiver
		expectedErr         error
		expectedProvenances map[string]models.Provenance
		opts                []createReceiverServiceSutOpt
	}{
		{
			name:                "service creates receiver",
			user:                writer,
			receiver:            baseReceiver.Clone(),
			expectedCreate:      models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.Encrypted(models.Base64Enrypt)),
			expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceNone},
		},
		{
			name:                "service creates receiver with multiple integrations",
			user:                writer,
			receiver:            models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithIntegrations(slackIntegration, emailIntegration)),
			expectedCreate:      models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithIntegrations(slackIntegration, emailIntegration), models.ReceiverMuts.Encrypted(models.Base64Enrypt)),
			expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceNone, emailIntegration.UID: models.ProvenanceNone},
		},
		{
			name:                "service creates receiver with provenance",
			user:                writer,
			receiver:            models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithProvenance(models.ProvenanceAPI), models.ReceiverMuts.WithIntegrations(slackIntegration, emailIntegration)),
			expectedCreate:      models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithProvenance(models.ProvenanceAPI), models.ReceiverMuts.WithIntegrations(slackIntegration, emailIntegration), models.ReceiverMuts.Encrypted(models.Base64Enrypt)),
			expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceAPI, emailIntegration.UID: models.ProvenanceAPI},
		},
		{
			name:        "existing receiver fails",
			user:        writer,
			receiver:    models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithName("grafana-default-email")),
			expectedErr: legacy_storage.ErrReceiverExists,
		},
		{
			name: "create integration with empty UID generates a new UID",
			user: writer,
			receiver: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration, models.IntegrationMuts.WithUID("")),
				models.CopyIntegrationWith(emailIntegration, models.IntegrationMuts.WithUID("")),
			)),
			expectedCreate: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration, models.IntegrationMuts.WithUID(generated(0))), // Mark UIDs as generated so that test will insert generated UID.
				models.CopyIntegrationWith(emailIntegration, models.IntegrationMuts.WithUID(generated(1))),
			), models.ReceiverMuts.Encrypted(models.Base64Enrypt)),
			expectedProvenances: map[string]models.Provenance{generated(0): models.ProvenanceNone, generated(1): models.ProvenanceNone}, // Mark UIDs as generated so that test will insert generated UID.
		},
		{
			name:        "create receiver with non-Grafana origin fails",
			user:        writer,
			receiver:    models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithOrigin(models.ResourceOriginImported)),
			expectedErr: ErrReceiverOrigin,
		},
		{
			name: "create integration with invalid UID fails",
			user: writer,
			receiver: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration, models.IntegrationMuts.WithUID("///@#$%^&*(")),
			)),
			expectedErr: legacy_storage.ErrReceiverInvalid,
		},
		{
			name: "create integration with existing UID fails",
			user: writer,
			receiver: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration, models.IntegrationMuts.WithUID("UID1")), // UID of grafana-default-email.
			)),
			expectedErr: legacy_storage.ErrReceiverInvalid,
		},
		{
			name:        "create with invalid integration fails",
			user:        writer,
			receiver:    models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithInvalidIntegration("slack")),
			expectedErr: legacy_storage.ErrReceiverInvalid,
		},
		{
			name: "create integration with no normal settings should not store nil settings",
			user: writer,
			receiver: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithIntegrations(
				models.CopyIntegrationWith(lineIntegration,
					models.IntegrationMuts.WithSettings(
						map[string]any{ // Line is valid with only the single secure field "token", so Settings will be empty when saving.
							"token": "secret",
						},
					)),
			)),
			expectedCreate: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithIntegrations(
				models.CopyIntegrationWith(lineIntegration,
					models.IntegrationMuts.WithSettings(
						map[string]any{}, // Empty settings, not nil.
					),
					models.IntegrationMuts.WithSecureSettings(
						map[string]string{
							"token": "c2VjcmV0", // base64 encoded "secret".
						},
					),
				),
			)),
			expectedStored: &definitions.PostableApiReceiver{
				Receiver: config.Receiver{
					Name: lineIntegration.Name,
				},
				PostableGrafanaReceivers: definitions.PostableGrafanaReceivers{
					GrafanaManagedReceivers: []*definitions.PostableGrafanaReceiver{
						{
							UID:                   lineIntegration.UID,
							Name:                  lineIntegration.Name,
							Type:                  string(lineIntegration.Config.Type()),
							DisableResolveMessage: lineIntegration.DisableResolveMessage,
							Settings:              definitions.RawMessage(`{}`), // Empty settings, not nil.
							SecureSettings: map[string]string{
								"token": "c2VjcmV0", // base64 encoded "secret".
							},
						},
					},
				},
			},
		},
		{
			name:        "receiver with empty name fails",
			user:        writer,
			receiver:    models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithName("")),
			expectedErr: legacy_storage.ErrReceiverInvalid,
		},
		{
			name:     "should be able to create receiver with the same name as imported ones",
			user:     writer,
			receiver: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithName("receiver1")),
			expectedCreate: models.CopyReceiverWith(baseReceiver,
				models.ReceiverMuts.Encrypted(models.Base64Enrypt),
				models.ReceiverMuts.WithName("receiver1"),
			),
			expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceNone},
			opts:                []createReceiverServiceSutOpt{withImportedIncluded},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			sut := createReceiverServiceSut(t, &secretsService, tc.opts...)

			created, err := sut.CreateReceiver(context.Background(), &tc.receiver, tc.user.GetOrgID(), tc.user)
			if tc.expectedErr == nil {
				require.NoError(t, err)
			} else {
				assert.ErrorIs(t, err, tc.expectedErr)
				return
			}

			// First verify generated UIDs. We can't compare set them directly in expected because they are generated,
			// so we ensure that all empty UIDs in expectedUpdate are not empty in updated.
			generatedUIDs := make(map[string]string)
			for i, integration := range tc.expectedCreate.Integrations {
				if isGenerated(integration.UID) {
					// Check that the UID was, in fact, generated.
					if created.Integrations[i].UID != "" {
						generatedUIDs[integration.UID] = created.Integrations[i].UID
						// This ensures the following assert.Equal will pass for this generated field.
						integration.UID = created.Integrations[i].UID
					}
				}
			}
			if len(generatedUIDs) > 0 {
				// Version was calculated without generated UIDs.
				tc.expectedCreate.Version = tc.expectedCreate.Fingerprint()

				// Set UIDs in expected provenance.
				for k, v := range tc.expectedProvenances {
					if gen, ok := generatedUIDs[k]; ok {
						tc.expectedProvenances[gen] = v
						delete(tc.expectedProvenances, k)
					}
				}
			}

			assert.Equal(t, tc.expectedCreate, *created)

			// Ensure receiver saved to store is correct.
			q := models.GetReceiverQuery{OrgID: tc.user.GetOrgID(), Name: tc.receiver.Name, Decrypt: true}
			stored, err := sut.GetReceiver(context.Background(), q, decryptUser)
			require.NoError(t, err)
			decrypted := models.CopyReceiverWith(tc.expectedCreate, models.ReceiverMuts.Decrypted(models.Base64Decrypt))
			decrypted.Version = tc.expectedCreate.Version // Version is calculated before decryption.
			assert.Equal(t, decrypted, *stored)

			if tc.expectedProvenances != nil {
				provenances, err := sut.provisioningStore.GetProvenances(context.Background(), tc.user.GetOrgID(), (&definitions.EmbeddedContactPoint{}).ResourceType())
				require.NoError(t, err)
				assert.Equal(t, tc.expectedProvenances, provenances)
			}

			if tc.expectedStored != nil {
				revision, err := sut.cfgStore.Get(context.Background(), writer.GetOrgID())
				require.NoError(t, err)
				for _, apiReceiver := range revision.Config.AlertmanagerConfig.Receivers {
					if apiReceiver.Name == tc.expectedStored.Name {
						assert.Equal(t, tc.expectedStored, apiReceiver)
						return
					}
				}
				t.Fatalf("expected to find receiver %q in revision", tc.expectedStored.Name)
			}
		})
	}
}

func TestReceiverService_Update(t *testing.T) {
	secretsService := fake_secrets.NewFakeSecretsService()

	writer := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
		1: {
			accesscontrol.ActionAlertingNotificationsWrite: nil,
			accesscontrol.ActionAlertingNotificationsRead:  nil,
		},
	}}
	decryptUser := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
		1: {
			accesscontrol.ActionAlertingReceiversReadSecrets: {models.ScopeReceiversAll},
		},
	}}

	// Used to mark generated fields to replace during test runtime.
	generated := func(n int) string { return fmt.Sprintf("[GENERATED]%d", n) }
	isGenerated := func(s string) bool { return strings.HasPrefix(s, "[GENERATED]") }

	rm := models.ReceiverMuts
	im := models.IntegrationMuts
	slackIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("slack"))()
	emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email"))()
	baseReceiver := models.ReceiverGen(models.ReceiverMuts.WithName("test receiver"), models.ReceiverMuts.WithIntegrations(slackIntegration))()

	for _, tc := range []struct {
		name                string
		user                identity.Requester
		receiver            models.Receiver
		version             string
		secureFields        map[string][]string
		existing            *models.Receiver
		expectedUpdate      models.Receiver
		expectedProvenances map[string]models.Provenance
		expectedErr         error
		opts                []createReceiverServiceSutOpt
	}{
		{
			name: "copies existing secure fields",
			user: writer,
			receiver: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration, im.AddSetting("newField", "newValue"))),
			),
			secureFields: map[string][]string{slackIntegration.UID: {"token"}},
			existing: util.Pointer(models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration,
					im.AddSecureSetting("token", "ZXhpc3RpbmdUb2tlbg=="), // This will get copied.
					im.AddSecureSetting("url", "ZXhpc3RpbmdVcmw="),       // This won't get copied.
				),
			))),
			expectedUpdate: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration,
					im.AddSetting("newField", "newValue"),
					im.AddSecureSetting("token", "ZXhpc3RpbmdUb2tlbg==")),
			), rm.Encrypted(models.Base64Enrypt)),
			expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceNone},
		},
		{
			name: "encrypts previously unencrypted secure fields",
			user: writer,
			receiver: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration, im.AddSetting("token", "unencryptedValue"))),
			),
			existing: util.Pointer(models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration,
					im.AddSetting("token", "unencryptedValue"), // This will get encrypted.
				),
			))),
			expectedUpdate: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration,
					im.AddSecureSetting("token", "dW5lbmNyeXB0ZWRWYWx1ZQ==")),
			), rm.Encrypted(models.Base64Enrypt)),
			expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceNone},
		},
		{
			// This test is important for covering the rare case when an existing field is marked as secure.
			// The UI will receive the field as secure and, if unchanged, will pass it back on update as a secureField instead of a Setting.
			name: "encrypts previously unencrypted secure fields when passed in as secureFields",
			user: writer,
			receiver: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration, im.AddSetting("newField", "newValue"))),
			),
			secureFields: map[string][]string{slackIntegration.UID: {"token"}},
			existing: util.Pointer(models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration,
					im.AddSetting("token", "unencryptedValue"), // This will get encrypted.
				),
			))),
			expectedUpdate: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration,
					im.AddSetting("newField", "newValue"),
					im.AddSecureSetting("token", "dW5lbmNyeXB0ZWRWYWx1ZQ==")),
			), rm.Encrypted(models.Base64Enrypt)),
			expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceNone},
		},
		{
			name: "doesn't copy existing unsecure fields",
			user: writer,
			receiver: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration, im.AddSetting("newField", "newValue"))),
			),
			secureFields: map[string][]string{slackIntegration.UID: {"somefield"}},
			existing: util.Pointer(models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration,
					im.AddSetting("somefield", "somevalue"), // This won't get copied.
				),
			))),
			expectedUpdate: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(
				models.CopyIntegrationWith(slackIntegration,
					im.AddSetting("newField", "newValue")),
			), rm.Encrypted(models.Base64Enrypt)),
			expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceNone},
		},
		{
			name:     "creates new provenance when integration is added",
			user:     writer,
			receiver: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(slackIntegration, emailIntegration), models.ReceiverMuts.WithProvenance(models.ProvenanceFile)),
			existing: util.Pointer(models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(slackIntegration), models.ReceiverMuts.WithProvenance(models.ProvenanceFile))),
			expectedUpdate: models.CopyReceiverWith(baseReceiver,
				rm.WithIntegrations(slackIntegration, emailIntegration),
				models.ReceiverMuts.WithProvenance(models.ProvenanceFile),
				rm.Encrypted(models.Base64Enrypt)),
			expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceFile, emailIntegration.UID: models.ProvenanceFile},
		},
		{
			name:     "deletes old provenance when integration is removed",
			user:     writer,
			receiver: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(slackIntegration), models.ReceiverMuts.WithProvenance(models.ProvenanceFile)),
			existing: util.Pointer(models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(slackIntegration, emailIntegration), models.ReceiverMuts.WithProvenance(models.ProvenanceFile))),
			expectedUpdate: models.CopyReceiverWith(baseReceiver,
				rm.WithIntegrations(slackIntegration),
				models.ReceiverMuts.WithProvenance(models.ProvenanceFile),
				rm.Encrypted(models.Base64Enrypt)),
			expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceFile},
		},
		{
			name:        "changing provenance from something to None fails",
			user:        writer,
			receiver:    models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithProvenance(models.ProvenanceNone)),
			existing:    util.Pointer(models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithProvenance(models.ProvenanceFile))),
			expectedErr: validation.MakeErrProvenanceChangeNotAllowed(models.ProvenanceFile, models.ProvenanceNone),
		},
		{
			name:     "changing provenance from one type to another fails", // TODO: This should fail once we move from lenient to strict validation.
			user:     writer,
			receiver: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithProvenance(models.ProvenanceAPI)),
			existing: util.Pointer(models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithProvenance(models.ProvenanceFile))),
			// expectedErr: validation.MakeErrProvenanceChangeNotAllowed(models.ProvenanceFile, models.ProvenanceAPI),
			expectedUpdate: models.CopyReceiverWith(baseReceiver,
				models.ReceiverMuts.WithProvenance(models.ProvenanceAPI),
				rm.Encrypted(models.Base64Enrypt)),
			expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceAPI},
		},
		{
			name:        "update receiver with optimistic version mismatch fails",
			user:        writer,
			receiver:    baseReceiver.Clone(),
			version:     "wrong version",
			existing:    util.Pointer(baseReceiver.Clone()),
			expectedErr: ErrReceiverVersionConflict,
		},
		{
			name:        "update receiver that doesn't exist fails",
			user:        writer,
			receiver:    baseReceiver.Clone(),
			expectedErr: legacy_storage.ErrReceiverNotFound,
		},
		{
			name:     "update that adds new integration generates a new UID",
			user:     writer,
			receiver: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(slackIntegration, models.CopyIntegrationWith(emailIntegration, im.WithUID("")))),
			existing: util.Pointer(baseReceiver.Clone()),
			expectedUpdate: models.CopyReceiverWith(baseReceiver,
				rm.WithIntegrations(slackIntegration, models.CopyIntegrationWith(emailIntegration, im.WithUID(generated(0)))), // Mark UID as generated so that test will insert generated UID.
				rm.Encrypted(models.Base64Enrypt)),
			expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceNone, generated(0): models.ProvenanceNone}, // Mark UID as generated so that test will insert generated UID.
		},
		{
			name:        "update with integration that has a UID that already exists fails",
			user:        writer,
			receiver:    models.CopyReceiverWith(baseReceiver, rm.WithIntegrations(slackIntegration, models.CopyIntegrationWith(emailIntegration, im.WithUID(slackIntegration.UID)))),
			existing:    util.Pointer(baseReceiver.Clone()),
			expectedErr: legacy_storage.ErrReceiverInvalid,
		},
		{
			name:        "update with invalid integration fails",
			user:        writer,
			receiver:    models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithInvalidIntegration("slack")),
			existing:    util.Pointer(baseReceiver.Clone()),
			expectedErr: legacy_storage.ErrReceiverInvalid,
		},
		{
			name:        "receivers with non-Grafana origin are not accepted",
			user:        writer,
			receiver:    models.CopyReceiverWith(baseReceiver, rm.WithOrigin(models.ResourceOriginImported)),
			existing:    util.Pointer(baseReceiver.Clone()),
			expectedErr: ErrReceiverOrigin,
		},
		{
			name:        "receivers of non-Grafana origin cannot be updated",
			user:        writer,
			receiver:    models.CopyReceiverWith(baseReceiver, rm.WithName("receiver1")),
			expectedErr: ErrReceiverOrigin,
			opts:        []createReceiverServiceSutOpt{withImportedIncluded},
		},
		{
			name:           "update should not fail if imported cannot be included",
			user:           writer,
			receiver:       models.CopyReceiverWith(baseReceiver, rm.WithEmptyIntegrations()),
			existing:       util.Pointer(models.CopyReceiverWith(baseReceiver)),
			expectedUpdate: models.CopyReceiverWith(baseReceiver, rm.WithEmptyIntegrations()),
			opts:           []createReceiverServiceSutOpt{withImportedIncluded, withInvalidExtraConfig},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			sut := createReceiverServiceSut(t, &secretsService, tc.opts...)

			if tc.existing != nil {
				// Create route after receivers as they will be referenced.
				revision, err := sut.cfgStore.Get(context.Background(), tc.user.GetOrgID())
				require.NoError(t, err)
				created, err := revision.CreateReceiver(tc.existing)
				require.NoError(t, err)

				err = sut.cfgStore.Save(context.Background(), revision, tc.user.GetOrgID())
				require.NoError(t, err)

				for _, integration := range created.Integrations {
					target := definitions.EmbeddedContactPoint{UID: integration.UID}
					err = sut.provisioningStore.SetProvenance(context.Background(), &target, tc.user.GetOrgID(), created.Provenance)
					require.NoError(t, err)
				}

				if tc.version == "" {
					tc.version = created.Version
				}
			}

			tc.receiver.Version = tc.version
			updated, err := sut.UpdateReceiver(context.Background(), &tc.receiver, tc.secureFields, tc.user.GetOrgID(), tc.user)
			if tc.expectedErr == nil {
				require.NoError(t, err)
			} else {
				assert.ErrorIs(t, err, tc.expectedErr)
				return
			}

			// First verify generated UIDs. We can't compare set them directly in expected because they are generated,
			// so we ensure that all empty UIDs in expectedUpdate are not empty in updated.
			generatedUIDs := make(map[string]string)
			for i, integration := range tc.expectedUpdate.Integrations {
				if isGenerated(integration.UID) {
					// Check that the UID was, in fact, generated.
					if updated.Integrations[i].UID != "" {
						generatedUIDs[integration.UID] = updated.Integrations[i].UID
						// This ensures the following assert.Equal will pass for this generated field.
						integration.UID = updated.Integrations[i].UID
					}
				}
			}
			if len(generatedUIDs) > 0 {
				// Version was calculated without generated UIDs.
				tc.expectedUpdate.Version = tc.expectedUpdate.Fingerprint()

				// Set UIDs in expected provenance.
				for k, v := range tc.expectedProvenances {
					if gen, ok := generatedUIDs[k]; ok {
						tc.expectedProvenances[gen] = v
						delete(tc.expectedProvenances, k)
					}
				}
			}

			assert.Equal(t, tc.expectedUpdate, *updated)

			// Ensure receiver saved to store is correct.
			q := models.GetReceiverQuery{OrgID: tc.user.GetOrgID(), Name: tc.receiver.Name, Decrypt: true}
			stored, err := sut.GetReceiver(context.Background(), q, decryptUser)
			require.NoError(t, err)
			decrypted := models.CopyReceiverWith(tc.expectedUpdate, models.ReceiverMuts.Decrypted(models.Base64Decrypt))
			decrypted.Version = tc.expectedUpdate.Version // Version is calculated before decryption.
			assert.Equal(t, decrypted, *stored)

			provenances, err := sut.provisioningStore.GetProvenances(context.Background(), tc.user.GetOrgID(), (&definitions.EmbeddedContactPoint{}).ResourceType())
			require.NoError(t, err)
			if tc.expectedProvenances == nil {
				tc.expectedProvenances = make(map[string]models.Provenance)
			}
			assert.Equal(t, tc.expectedProvenances, provenances)
		})
	}
}

func TestReceiverService_UpdateReceiverName(t *testing.T) {
	// This test is to ensure that the receiver name is updated in routes and notification settings when the name is changed.
	writer := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
		1: {
			accesscontrol.ActionAlertingNotificationsWrite: nil,
			accesscontrol.ActionAlertingNotificationsRead:  nil,
		},
	}}

	secretsService := fake_secrets.NewFakeSecretsService()
	receiverName := "grafana-default-email"
	newReceiverName := "new-name"
	slackIntegration := models.IntegrationGen(models.IntegrationMuts.WithName(receiverName), models.IntegrationMuts.WithValidConfig("slack"))()
	baseReceiver := models.ReceiverGen(models.ReceiverMuts.WithName(receiverName), models.ReceiverMuts.WithIntegrations(slackIntegration))()
	baseReceiver.Version = "cd95627c75892a39" // Correct version for grafana-default-email.
	baseReceiver.Name = newReceiverName       // Done here instead of in a mutator so we keep the same uid.

	t.Run("renames receiver and all its dependencies", func(t *testing.T) {
		ruleStore := &fakeAlertRuleNotificationStore{}
		sut := createReceiverServiceSut(t, &secretsService)
		sut.ruleNotificationsStore = ruleStore

		_, err := sut.UpdateReceiver(context.Background(), &baseReceiver, nil, writer.GetOrgID(), writer)
		require.NoError(t, err)

		assert.Equal(t, "RenameReceiverInNotificationSettings", ruleStore.Calls[0].Method)
		assert.Equal(t, writer.OrgID, ruleStore.Calls[0].Args[1])
		assert.Equal(t, receiverName, ruleStore.Calls[0].Args[2])
		assert.Equal(t, newReceiverName, ruleStore.Calls[0].Args[3])
		assert.NotNil(t, ruleStore.Calls[0].Args[4])
		assert.Falsef(t, ruleStore.Calls[0].Args[5].(bool), "dryrun expected to be false")

		// Ensure receiver name is updated in routes.
		revision, err := sut.cfgStore.Get(context.Background(), writer.GetOrgID())
		require.NoError(t, err)

		assert.Falsef(t, revision.ReceiverNameUsedByRoutes(receiverName), "old receiver name '%s' should not be used by routes", receiverName)
		assert.Truef(t, revision.ReceiverNameUsedByRoutes(newReceiverName), "new receiver name '%s' should be used by routes", newReceiverName)
	})

	t.Run("returns ErrReceiverDependentResourcesProvenance if route has different provenance status", func(t *testing.T) {
		sut := createReceiverServiceSut(t, &secretsService)
		provenanceStore := sut.provisioningStore.(*fakes.FakeProvisioningStore)
		provenanceStore.Records[1] = map[string]models.Provenance{
			(&definitions.Route{}).ResourceType(): models.ProvenanceFile,
		}

		ruleStore := &fakeAlertRuleNotificationStore{
			RenameReceiverInNotificationSettingsFn: func(ctx context.Context, orgID int64, old, new string, validate func(models.Provenance) bool, dryRun bool) ([]models.AlertRuleKey, []models.AlertRuleKey, error) {
				assertInTransaction(t, ctx)
				return nil, nil, nil
			},
		}
		sut.ruleNotificationsStore = ruleStore

		_, err := sut.UpdateReceiver(context.Background(), &baseReceiver, nil, writer.GetOrgID(), writer)
		require.ErrorIs(t, err, ErrReceiverDependentResourcesProvenance)

		require.Len(t, ruleStore.Calls, 1)
		assert.Equal(t, "RenameReceiverInNotificationSettings", ruleStore.Calls[0].Method)
		assert.Equal(t, writer.OrgID, ruleStore.Calls[0].Args[1])
		assert.Equal(t, receiverName, ruleStore.Calls[0].Args[2])
		assert.Equal(t, newReceiverName, ruleStore.Calls[0].Args[3])
		assert.NotNil(t, ruleStore.Calls[0].Args[4])
		assert.True(t, ruleStore.Calls[0].Args[5].(bool)) // still check if there are rules that have incompatible provenance
	})

	t.Run("returns ErrReceiverDependentResourcesProvenance if rules have different provenance status", func(t *testing.T) {
		sut := createReceiverServiceSut(t, &secretsService)

		ruleStore := &fakeAlertRuleNotificationStore{
			RenameReceiverInNotificationSettingsFn: func(ctx context.Context, orgID int64, old, new string, validate func(models.Provenance) bool, dryRun bool) ([]models.AlertRuleKey, []models.AlertRuleKey, error) {
				assertInTransaction(t, ctx)
				return nil, []models.AlertRuleKey{models.GenerateRuleKey(orgID)}, nil
			},
		}
		sut.ruleNotificationsStore = ruleStore

		_, err := sut.UpdateReceiver(context.Background(), &baseReceiver, nil, writer.GetOrgID(), writer)
		require.ErrorIs(t, err, ErrReceiverDependentResourcesProvenance)

		require.Len(t, ruleStore.Calls, 1)
		assert.Equal(t, "RenameReceiverInNotificationSettings", ruleStore.Calls[0].Method)
		assert.Equal(t, writer.OrgID, ruleStore.Calls[0].Args[1])
		assert.Equal(t, receiverName, ruleStore.Calls[0].Args[2])
		assert.Equal(t, newReceiverName, ruleStore.Calls[0].Args[3])
		assert.NotNil(t, ruleStore.Calls[0].Args[4])
		assert.Falsef(t, ruleStore.Calls[0].Args[5].(bool), "dryrun expected to be false")
	})

	t.Run("returns ErrReceiverInvalid if empty name", func(t *testing.T) {
		ruleStore := &fakeAlertRuleNotificationStore{}
		sut := createReceiverServiceSut(t, &secretsService)
		sut.ruleNotificationsStore = ruleStore
		baseReceiver.Name = ""

		_, err := sut.UpdateReceiver(context.Background(), &baseReceiver, nil, writer.GetOrgID(), writer)
		require.ErrorIs(t, err, legacy_storage.ErrReceiverInvalid)
	})

	t.Run("can rename receiver to name that is already used by another receiver of different origin", func(t *testing.T) {
		ruleStore := &fakeAlertRuleNotificationStore{}
		sut := createReceiverServiceSut(t, &secretsService, withImportedIncluded)
		sut.ruleNotificationsStore = ruleStore

		newReceiverName = "receiver1"
		actual, err := sut.GetReceiver(context.Background(), models.GetReceiverQuery{OrgID: writer.GetOrgID(), Name: newReceiverName}, writer)
		require.NoError(t, err)
		require.Equal(t, models.ResourceOriginImported, actual.Origin)
		require.Equal(t, newReceiverName, actual.Name)
		require.NotEmpty(t, actual.Integrations)

		baseReceiver.Name = newReceiverName

		recv, err := sut.UpdateReceiver(context.Background(), &baseReceiver, nil, writer.GetOrgID(), writer)
		require.NoError(t, err)
		require.NotEqual(t, actual, recv)
		require.Equal(t, models.ResourceOriginGrafana, recv.Origin)

		actual, err = sut.GetReceiver(context.Background(), models.GetReceiverQuery{OrgID: writer.GetOrgID(), Name: newReceiverName}, writer)
		require.NoError(t, err)
		require.Equal(t, recv.Name, actual.Name)
	})
}

func TestReceiverServiceAC_Read(t *testing.T) {
	var orgId int64 = 1
	secretsService := fake_secrets.NewFakeSecretsService()

	admin := &user.SignedInUser{OrgID: orgId, OrgRole: org.RoleAdmin, Permissions: map[int64]map[string][]string{
		orgId: {
			accesscontrol.ActionAlertingNotificationsWrite: nil,
			accesscontrol.ActionAlertingNotificationsRead:  nil,
		},
	}}

	slackIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("slack"))
	emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email"))
	recv1 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver1"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
	recv2 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver2"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
	recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver with a really long name that surpasses 40 characters"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
	allReceivers := func() []models.Receiver {
		return []models.Receiver{recv1, recv2, recv3}
	}
	testCases := []struct {
		name        string
		permissions map[string][]string
		existing    []models.Receiver

		visible                 []models.Receiver
		visibleWithProvisioning []models.Receiver
	}{
		{
			name:     "not authorized without permissions",
			existing: allReceivers(),
			visible:  nil,
		},
		{
			name:        "not authorized without receivers scope",
			permissions: map[string][]string{accesscontrol.ActionAlertingReceiversRead: nil},
			existing:    allReceivers(),
			visible:     nil,
		},
		{
			name:        "global legacy permissions - read all",
			permissions: map[string][]string{accesscontrol.ActionAlertingNotificationsRead: nil},
			existing:    allReceivers(),
			visible:     allReceivers(),
		},
		{
			name:        "global receivers permissions - read all",
			permissions: map[string][]string{accesscontrol.ActionAlertingReceiversRead: {models.ScopeReceiversAll}},
			existing:    allReceivers(),
			visible:     allReceivers(),
		},
		{
			name: "single receivers permissions - read some",
			permissions: map[string][]string{accesscontrol.ActionAlertingReceiversRead: {
				models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID),
				models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID),
			}},
			existing: allReceivers(),
			visible:  []models.Receiver{recv1, recv3},
		},
		{
			name:        "global receivers secret permissions - read all",
			permissions: map[string][]string{accesscontrol.ActionAlertingReceiversReadSecrets: {models.ScopeReceiversAll}},
			existing:    allReceivers(),
			visible:     allReceivers(),
		},
		{
			name: "single receivers secret permissions - read some",
			permissions: map[string][]string{accesscontrol.ActionAlertingReceiversReadSecrets: {
				models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID),
				models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID),
			}},
			existing: allReceivers(),
			visible:  []models.Receiver{recv1, recv3},
		},
		{
			name:                    "provisioning read applies to only provisioning",
			permissions:             map[string][]string{accesscontrol.ActionAlertingProvisioningRead: nil},
			existing:                allReceivers(),
			visible:                 nil,
			visibleWithProvisioning: allReceivers(),
		},
		{
			name:                    "provisioning read secrets applies to only provisioning",
			permissions:             map[string][]string{accesscontrol.ActionAlertingProvisioningReadSecrets: nil},
			existing:                allReceivers(),
			visible:                 nil,
			visibleWithProvisioning: allReceivers(),
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			sut := createReceiverServiceSut(t, &secretsService)

			for _, recv := range tc.existing {
				_, err := sut.CreateReceiver(context.Background(), &recv, orgId, admin)
				require.NoError(t, err)
			}

			usr := &user.SignedInUser{OrgID: orgId, Permissions: map[int64]map[string][]string{
				orgId: tc.permissions,
			}}

			isVisible := func(uid string) bool {
				for _, recv := range tc.visible {
					if recv.UID == uid {
						return true
					}
				}
				return false
			}
			for _, recv := range allReceivers() {
				response, err := sut.GetReceiver(context.Background(), singleQ(orgId, recv.Name), usr)
				if isVisible(recv.UID) {
					require.NoErrorf(t, err, "receiver '%s' should be visible, but isn't", recv.Name)
					assert.NotNil(t, response)
				} else {
					assert.ErrorIsf(t, err, ac.ErrAuthorizationBase, "receiver '%s' should not be visible, but is", recv.Name)
				}
			}

			isVisibleInProvisioning := func(uid string) bool {
				if tc.visibleWithProvisioning == nil {
					return isVisible(uid)
				}
				for _, recv := range tc.visibleWithProvisioning {
					if recv.UID == uid {
						return true
					}
				}
				return false
			}
			sut.authz = ac.NewReceiverAccess[*models.Receiver](acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), true)
			for _, recv := range allReceivers() {
				response, err := sut.GetReceiver(context.Background(), singleQ(orgId, recv.Name), usr)
				if isVisibleInProvisioning(recv.UID) {
					require.NoErrorf(t, err, "receiver '%s' should be visible, but isn't", recv.Name)
					assert.NotNil(t, response)
				} else {
					assert.ErrorIsf(t, err, ac.ErrAuthorizationBase, "receiver '%s' should not be visible, but is", recv.Name)
				}
			}
		})
	}
}

func TestReceiverServiceAC_Create(t *testing.T) {
	var orgId int64 = 1
	secretsService := fake_secrets.NewFakeSecretsService()

	slackIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("slack"))
	emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email"))
	recv1 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver1"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
	recv2 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver2"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
	recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver with a really long name that surpasses 40 characters"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
	allReceivers := func() []models.Receiver {
		return []models.Receiver{recv1, recv2, recv3}
	}
	testCases := []struct {
		name        string
		permissions map[string][]string

		hasAccess []models.Receiver
	}{
		{
			name:      "not authorized without permissions",
			hasAccess: nil,
		},
		{
			name:        "global legacy permissions - authorized without read",
			permissions: map[string][]string{accesscontrol.ActionAlertingNotificationsWrite: nil},
			hasAccess:   allReceivers(),
		},
		{
			name:        "receivers permissions - authorized without read",
			permissions: map[string][]string{accesscontrol.ActionAlertingReceiversCreate: nil},
			hasAccess:   allReceivers(),
		},
		{
			name:        "global legacy permissions - create all",
			permissions: map[string][]string{accesscontrol.ActionAlertingNotificationsWrite: nil, accesscontrol.ActionAlertingNotificationsRead: nil},
			hasAccess:   allReceivers(),
		},
		{
			name:        "receivers permissions - create all",
			permissions: map[string][]string{accesscontrol.ActionAlertingReceiversCreate: nil, accesscontrol.ActionAlertingReceiversRead: nil},
			hasAccess:   allReceivers(),
		},
		{
			name: "receivers mixed global read permissions - create all",
			permissions: map[string][]string{
				accesscontrol.ActionAlertingReceiversCreate:   nil,
				accesscontrol.ActionAlertingNotificationsRead: nil,
			},
			hasAccess: allReceivers(),
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			sut := createReceiverServiceSut(t, &secretsService)

			usr := &user.SignedInUser{OrgID: orgId, Permissions: map[int64]map[string][]string{
				orgId: tc.permissions,
			}}

			hasAccess := func(uid string) bool {
				for _, recv := range tc.hasAccess {
					if recv.UID == uid {
						return true
					}
				}
				return false
			}
			for _, recv := range allReceivers() {
				response, err := sut.CreateReceiver(context.Background(), &recv, orgId, usr)
				if hasAccess(recv.UID) {
					require.NoErrorf(t, err, "should have access to receiver '%s', but doesn't", recv.Name)
					assert.NotNil(t, response)
				} else {
					assert.ErrorIsf(t, err, ac.ErrAuthorizationBase, "should not have access to receiver '%s', but does", recv.Name)
				}
			}
		})
	}
}

func TestReceiverServiceAC_Update(t *testing.T) {
	var orgId int64 = 1
	secretsService := fake_secrets.NewFakeSecretsService()

	admin := &user.SignedInUser{OrgID: orgId, OrgRole: org.RoleAdmin, Permissions: map[int64]map[string][]string{
		orgId: {
			accesscontrol.ActionAlertingNotificationsWrite: nil,
			accesscontrol.ActionAlertingNotificationsRead:  nil,
		},
	}}

	slackIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("slack"))
	emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email"))
	recv1 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver1"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
	recv2 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver2"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
	recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver with a really long name that surpasses 40 characters"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
	allReceivers := func() []models.Receiver {
		return []models.Receiver{recv1, recv2, recv3}
	}
	testCases := []struct {
		name        string
		permissions map[string][]string
		existing    []models.Receiver

		hasAccess []models.Receiver
	}{
		{
			name:      "not authorized without permissions",
			existing:  allReceivers(),
			hasAccess: nil,
		},
		{
			name:        "not authorized without receivers scope",
			permissions: map[string][]string{accesscontrol.ActionAlertingReceiversUpdate: nil},
			existing:    allReceivers(),
			hasAccess:   nil,
		},
		{
			name:        "global legacy permissions - not authorized without read",
			permissions: map[string][]string{accesscontrol.ActionAlertingNotificationsWrite: nil},
			existing:    allReceivers(),
			hasAccess:   nil,
		},
		{
			name:        "global receivers permissions - not authorized without read",
			permissions: map[string][]string{accesscontrol.ActionAlertingReceiversUpdate: {models.ScopeReceiversAll}},
			existing:    allReceivers(),
			hasAccess:   nil,
		},
		{
			name: "single receivers permissions - not authorized without read",
			permissions: map[string][]string{accesscontrol.ActionAlertingReceiversUpdate: {
				models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID),
				models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID),
			}},
			existing:  allReceivers(),
			hasAccess: nil,
		},
		{
			name:        "global legacy permissions - update all",
			permissions: map[string][]string{accesscontrol.ActionAlertingNotificationsWrite: nil, accesscontrol.ActionAlertingNotificationsRead: nil},
			existing:    allReceivers(),
			hasAccess:   allReceivers(),
		},
		{
			name:        "global receivers permissions - update all",
			permissions: map[string][]string{accesscontrol.ActionAlertingReceiversUpdate: {models.ScopeReceiversAll}, accesscontrol.ActionAlertingReceiversRead: {models.ScopeReceiversAll}},
			existing:    allReceivers(),
			hasAccess:   allReceivers(),
		},
		{
			name: "single receivers permissions - update some",
			permissions: map[string][]string{
				accesscontrol.ActionAlertingReceiversUpdate: {
					models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID),
					models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID),
				},
				accesscontrol.ActionAlertingReceiversRead: {
					models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID),
					models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID),
				},
			},
			existing:  allReceivers(),
			hasAccess: []models.Receiver{recv1, recv3},
		},
		{
			name: "single receivers mixed read permissions - update some",
			permissions: map[string][]string{
				accesscontrol.ActionAlertingReceiversUpdate: {
					models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID),
					models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID),
				},
				accesscontrol.ActionAlertingReceiversRead: {
					models.ScopeReceiversProvider.GetResourceScopeUID(recv2.UID),
					models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID),
				},
			},
			existing:  allReceivers(),
			hasAccess: []models.Receiver{recv3},
		},
		{
			name: "single receivers mixed global read permissions - update some",
			permissions: map[string][]string{
				accesscontrol.ActionAlertingReceiversUpdate: {
					models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID),
					models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID),
				},
				accesscontrol.ActionAlertingNotificationsRead: nil,
			},
			existing:  allReceivers(),
			hasAccess: []models.Receiver{recv1, recv3},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			sut := createReceiverServiceSut(t, &secretsService)

			versions := map[string]string{}
			for _, recv := range tc.existing {
				created, err := sut.CreateReceiver(context.Background(), &recv, orgId, admin)
				require.NoError(t, err)
				versions[recv.UID] = created.Version
			}

			usr := &user.SignedInUser{OrgID: orgId, Permissions: map[int64]map[string][]string{
				orgId: tc.permissions,
			}}

			hasAccess := func(uid string) bool {
				for _, recv := range tc.hasAccess {
					if recv.UID == uid {
						return true
					}
				}
				return false
			}
			for _, recv := range allReceivers() {
				clone := recv.Clone()
				clone.Version = versions[recv.UID]
				response, err := sut.UpdateReceiver(context.Background(), &clone, nil, orgId, usr)
				if hasAccess(clone.UID) {
					require.NoErrorf(t, err, "should have access to receiver '%s', but doesn't", clone.Name)
					assert.NotNil(t, response)
				} else {
					assert.ErrorIsf(t, err, ac.ErrAuthorizationBase, "should not have access to receiver '%s', but does", clone.Name)
				}
			}
		})
	}
}

func TestReceiverServiceAC_Delete(t *testing.T) {
	var orgId int64 = 1
	secretsService := fake_secrets.NewFakeSecretsService()

	admin := &user.SignedInUser{OrgID: orgId, OrgRole: org.RoleAdmin, Permissions: map[int64]map[string][]string{
		orgId: {
			accesscontrol.ActionAlertingNotificationsWrite: nil,
			accesscontrol.ActionAlertingNotificationsRead:  nil,
		},
	}}

	slackIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("slack"))
	emailIntegration := models.IntegrationGen(models.IntegrationMuts.WithName("test receiver"), models.IntegrationMuts.WithValidConfig("email"))
	recv1 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver1"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
	recv2 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver2"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
	recv3 := models.ReceiverGen(models.ReceiverMuts.WithName("receiver with a really long name that surpasses 40 characters"), models.ReceiverMuts.WithIntegrations(slackIntegration(), emailIntegration()))()
	allReceivers := func() []models.Receiver {
		return []models.Receiver{recv1, recv2, recv3}
	}
	testCases := []struct {
		name        string
		permissions map[string][]string
		existing    []models.Receiver

		hasAccess []models.Receiver
	}{
		{
			name:      "not authorized without permissions",
			existing:  allReceivers(),
			hasAccess: nil,
		},
		{
			name:        "not authorized without receivers scope",
			permissions: map[string][]string{accesscontrol.ActionAlertingReceiversDelete: nil},
			existing:    allReceivers(),
			hasAccess:   nil,
		},
		{
			name:        "global legacy permissions - not authorized without read",
			permissions: map[string][]string{accesscontrol.ActionAlertingNotificationsWrite: nil},
			existing:    allReceivers(),
			hasAccess:   nil,
		},
		{
			name:        "global receivers permissions - not authorized without read",
			permissions: map[string][]string{accesscontrol.ActionAlertingReceiversDelete: {models.ScopeReceiversAll}},
			existing:    allReceivers(),
			hasAccess:   nil,
		},
		{
			name: "single receivers permissions - not authorized without read",
			permissions: map[string][]string{accesscontrol.ActionAlertingReceiversDelete: {
				models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID),
				models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID),
			}},
			existing:  allReceivers(),
			hasAccess: nil,
		},
		{
			name:        "global legacy permissions - delete all",
			permissions: map[string][]string{accesscontrol.ActionAlertingNotificationsWrite: nil, accesscontrol.ActionAlertingNotificationsRead: nil},
			existing:    allReceivers(),
			hasAccess:   allReceivers(),
		},
		{
			name:        "global receivers permissions - delete all",
			permissions: map[string][]string{accesscontrol.ActionAlertingReceiversDelete: {models.ScopeReceiversAll}, accesscontrol.ActionAlertingReceiversRead: {models.ScopeReceiversAll}},
			existing:    allReceivers(),
			hasAccess:   allReceivers(),
		},
		{
			name: "single receivers permissions - delete some",
			permissions: map[string][]string{
				accesscontrol.ActionAlertingReceiversDelete: {
					models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID),
					models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID),
				},
				accesscontrol.ActionAlertingReceiversRead: {
					models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID),
					models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID),
				},
			},
			existing:  allReceivers(),
			hasAccess: []models.Receiver{recv1, recv3},
		},
		{
			name: "single receivers mixed read permissions - delete some",
			permissions: map[string][]string{
				accesscontrol.ActionAlertingReceiversDelete: {
					models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID),
					models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID),
				},
				accesscontrol.ActionAlertingReceiversRead: {
					models.ScopeReceiversProvider.GetResourceScopeUID(recv2.UID),
					models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID),
				},
			},
			existing:  allReceivers(),
			hasAccess: []models.Receiver{recv3},
		},
		{
			name: "single receivers mixed global read permissions - delete some",
			permissions: map[string][]string{
				accesscontrol.ActionAlertingReceiversDelete: {
					models.ScopeReceiversProvider.GetResourceScopeUID(recv1.UID),
					models.ScopeReceiversProvider.GetResourceScopeUID(recv3.UID),
				},
				accesscontrol.ActionAlertingNotificationsRead: nil,
			},
			existing:  allReceivers(),
			hasAccess: []models.Receiver{recv1, recv3},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			sut := createReceiverServiceSut(t, &secretsService)

			versions := map[string]string{}
			for _, recv := range tc.existing {
				created, err := sut.CreateReceiver(context.Background(), &recv, orgId, admin)
				require.NoError(t, err)
				versions[recv.UID] = created.Version
			}

			usr := &user.SignedInUser{OrgID: orgId, Permissions: map[int64]map[string][]string{
				orgId: tc.permissions,
			}}

			hasAccess := func(uid string) bool {
				for _, recv := range tc.hasAccess {
					if recv.UID == uid {
						return true
					}
				}
				return false
			}
			for _, recv := range allReceivers() {
				err := sut.DeleteReceiver(context.Background(), recv.UID, models.ProvenanceNone, versions[recv.UID], orgId, usr)
				if hasAccess(recv.UID) {
					require.NoErrorf(t, err, "should have access to receiver '%s', but doesn't", recv.Name)
				} else {
					assert.ErrorIsf(t, err, ac.ErrAuthorizationBase, "should not have access to receiver '%s', but does", recv.Name)
				}
			}
		})
	}
}

func TestReceiverService_InUseMetadata(t *testing.T) {
	secretsService := fake_secrets.NewFakeSecretsService()

	admin := &user.SignedInUser{OrgID: 1, OrgRole: org.RoleAdmin, Permissions: map[int64]map[string][]string{
		1: {
			accesscontrol.ActionAlertingNotificationsWrite: nil,
			accesscontrol.ActionAlertingNotificationsRead:  nil,
		},
	}}

	for _, tc := range []struct {
		name             string
		user             identity.Requester
		storeRoute       definitions.Route
		storeSettings    map[models.AlertRuleKey][]models.NotificationSettings
		existing         []*models.Receiver
		expectedMetadata map[string]models.ReceiverMetadata
	}{
		{
			name: "mixed metadata",
			user: admin,
			existing: []*models.Receiver{
				util.Pointer(models.ReceiverGen(models.ReceiverMuts.WithName("receiver1"))()),
				util.Pointer(models.ReceiverGen(models.ReceiverMuts.WithName("receiver2"))()),
				util.Pointer(models.ReceiverGen(models.ReceiverMuts.WithName("receiver3"))()),
				util.Pointer(models.ReceiverGen(models.ReceiverMuts.WithName("receiver4"))()),
			},
			storeSettings: map[models.AlertRuleKey][]models.NotificationSettings{
				{OrgID: 1, UID: "rule1uid"}: {
					models.NotificationSettingsGen(models.NSMuts.WithReceiver("receiver1"))(),
					models.NotificationSettingsGen(models.NSMuts.WithReceiver("receiver2"))(),
				},
				{OrgID: 1, UID: "rule2uid"}: {
					models.NotificationSettingsGen(models.NSMuts.WithReceiver("receiver2"))(),
					models.NotificationSettingsGen(models.NSMuts.WithReceiver("receiver3"))(),
				},
			},
			storeRoute: definitions.Route{
				Receiver: "receiver1",
				Routes: []*definitions.Route{
					{Receiver: "receiver2"},
					{Receiver: "receiver3"},
					{
						Receiver: "receiver4",
						Routes: []*definitions.Route{
							{Receiver: "receiver1"},
							{Receiver: "receiver3"},
						},
					},
				},
			},
			expectedMetadata: map[string]models.ReceiverMetadata{
				legacy_storage.NameToUid("receiver1"): {
					InUseByRules:  []models.AlertRuleKey{{OrgID: 1, UID: "rule1uid"}},
					InUseByRoutes: 2,
					CanUse:        true,
				},
				legacy_storage.NameToUid("receiver2"): {
					InUseByRules:  []models.AlertRuleKey{{OrgID: 1, UID: "rule1uid"}, {OrgID: 1, UID: "rule2uid"}},
					InUseByRoutes: 1,
					CanUse:        true,
				},
				legacy_storage.NameToUid("receiver3"): {
					InUseByRules:  []models.AlertRuleKey{{OrgID: 1, UID: "rule2uid"}},
					InUseByRoutes: 2,
					CanUse:        true,
				},
				legacy_storage.NameToUid("receiver4"): {
					InUseByRules:  []models.AlertRuleKey{},
					InUseByRoutes: 1,
					CanUse:        true,
				},
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			store := &fakeAlertRuleNotificationStore{}
			store.ListNotificationSettingsFn = func(ctx context.Context, q models.ListNotificationSettingsQuery) (map[models.AlertRuleKey][]models.NotificationSettings, error) {
				return tc.storeSettings, nil
			}

			sut := createReceiverServiceSut(t, &secretsService)
			sut.ruleNotificationsStore = store

			for _, recv := range tc.existing {
				_, err := sut.CreateReceiver(context.Background(), recv, tc.user.GetOrgID(), tc.user)
				require.NoError(t, err)
			}

			// Create route after receivers as they will be referenced.
			revision, err := sut.cfgStore.Get(context.Background(), tc.user.GetOrgID())
			require.NoError(t, err)
			revision.Config.AlertmanagerConfig.Route = &tc.storeRoute
			err = sut.cfgStore.Save(context.Background(), revision, tc.user.GetOrgID())
			require.NoError(t, err)

			metadata, err := sut.InUseMetadata(context.Background(), tc.user.GetOrgID(), tc.existing...)
			require.NoError(t, err)

			assert.Lenf(t, metadata, len(tc.expectedMetadata), "unexpected metadata length")
			for _, recv := range tc.existing {
				expected, ok := tc.expectedMetadata[recv.UID]
				assert.Truef(t, ok, "missing metadata for receiver uid: %q, name: %q", recv.UID, recv.Name)
				assert.ElementsMatch(t, expected.InUseByRules, metadata[recv.UID].InUseByRules, "unexpected rules metadata for receiver uid: %q, name: %q", recv.UID, recv.Name)
				assert.Equalf(t, expected.InUseByRoutes, metadata[recv.UID].InUseByRoutes, "unexpected routes metadata for receiver uid: %q, name: %q", recv.UID, recv.Name)
			}
		})
	}
}

func TestReceiverService_AccessControlMetadata(t *testing.T) {
	secretsService := fake_secrets.NewFakeSecretsService()
	sut := createReceiverServiceSut(t, &secretsService, withImportedIncluded)

	admin := &user.SignedInUser{OrgID: 1, OrgRole: org.RoleAdmin, Permissions: map[int64]map[string][]string{
		1: {
			accesscontrol.ActionAlertingNotificationsWrite: nil,
			accesscontrol.ActionAlertingNotificationsRead:  nil,
			accesscontrol.ActionAlertingReceiversReadSecrets: []string{
				models.ScopeReceiversProvider.GetResourceAllScope(),
			},
		},
	}}

	r, err := sut.GetReceiver(context.Background(), models.GetReceiverQuery{OrgID: 1, Name: "receiver1"}, admin)
	require.NoError(t, err)

	t.Run("should override metadata for imported receivers", func(t *testing.T) {
		meta, err := sut.AccessControlMetadata(context.Background(), admin, r)
		require.NoError(t, err)
		expectedPermissions := models.NewReceiverPermissionSet()
		expectedPermissions.Set(models.ReceiverPermissionAdmin, false)
		expectedPermissions.Set(models.ReceiverPermissionWrite, false)
		expectedPermissions.Set(models.ReceiverPermissionDelete, false)
		expectedPermissions.Set(models.ReceiverPermissionReadSecret, true)

		expected := map[string]models.ReceiverPermissionSet{
			r.GetUID(): expectedPermissions,
		}
		assert.Equal(t, expected, meta)
	})
}

type createReceiverServiceSutOpt func(*testing.T, *ReceiverService)

func withInvalidExtraConfig(t *testing.T, sut *ReceiverService) {
	t.Helper()
	extra := getExtraConfig()
	extra.AlertmanagerConfig = "yaml:invalid"
	cfg := createEncryptedConfig(t, sut.encryptionService, extra)
	store := fakes.NewFakeAlertmanagerConfigStore(cfg)
	sut.cfgStore = legacy_storage.NewAlertmanagerConfigStore(store, NewExtraConfigsCrypto(sut.encryptionService))
}

func withImportedIncluded(_ *testing.T, sut *ReceiverService) {
	sut.includeImported = true
}

func createReceiverServiceSut(t *testing.T, encryptSvc secretService, opts ...createReceiverServiceSutOpt) *ReceiverService {
	cfg := createEncryptedConfig(t, encryptSvc, getExtraConfig())
	store := fakes.NewFakeAlertmanagerConfigStore(cfg)
	xact := newNopTransactionManager()
	provisioningStore := fakes.NewFakeProvisioningStore()

	sut := NewReceiverService(
		ac.NewReceiverAccess[*models.Receiver](acimpl.ProvideAccessControl(featuremgmt.WithFeatures()), false),
		legacy_storage.NewAlertmanagerConfigStore(store, NewExtraConfigsCrypto(encryptSvc)),
		provisioningStore,
		&fakeAlertRuleNotificationStore{},
		encryptSvc,
		xact,
		log.NewNopLogger(),
		fakes.NewFakeReceiverPermissionsService(),
		tracing.InitializeTracerForTest(),
		false,
	)
	for _, opt := range opts {
		opt(t, sut)
	}
	return sut
}

func createEncryptedConfig(t *testing.T, secretService secretService, extraConfig *definitions.ExtraConfiguration) string {
	c := &definitions.PostableUserConfig{}
	err := json.Unmarshal([]byte(defaultAlertmanagerConfigJSON), c)
	require.NoError(t, err)
	err = EncryptReceiverConfigs(c.AlertmanagerConfig.Receivers, func(ctx context.Context, payload []byte) ([]byte, error) {
		return secretService.Encrypt(ctx, payload, secrets.WithoutScope())
	})
	require.NoError(t, err)
	if extraConfig != nil {
		c.ExtraConfigs = append(c.ExtraConfigs, *extraConfig)
		require.NoError(t, NewExtraConfigsCrypto(secretService).EncryptExtraConfigs(context.Background(), c))
	}
	bytes, err := json.Marshal(c)
	require.NoError(t, err)
	return string(bytes)
}

func singleQ(orgID int64, name string) models.GetReceiverQuery {
	return models.GetReceiverQuery{
		OrgID: orgID,
		Name:  name,
	}
}

func multiQ(orgID int64, names ...string) models.GetReceiversQuery {
	return models.GetReceiversQuery{
		OrgID: orgID,
		Names: names,
	}
}

const defaultAlertmanagerConfigJSON = `
{
	"template_files": null,
	"alertmanager_config": {
		"route": {
			"receiver": "grafana-default-email",
			"group_by": [
				"..."
			],
			"routes": [{
				"receiver": "grafana-default-email",
				"object_matchers": [["a", "=", "b"]]
			}]
		},
		"templates": null,
		"receivers": [{
			"name": "grafana-default-email",
			"grafana_managed_receiver_configs": [{
				"uid": "UID1",
				"name": "grafana-default-email",
				"type": "email",
				"disableResolveMessage": false,
				"settings": {
					"addresses": "\u003cexample@email.com\u003e"
				},
				"secureFields": {}
			}]
		}, {
			"name": "slack receiver",
			"grafana_managed_receiver_configs": [{
				"uid": "UID2",
				"name": "slack receiver",
				"type": "slack",
				"disableResolveMessage": false,
				"settings": {},
				"secureSettings": {"url":"secure url"}
			}]
		}]
	}
}
`

func getExtraConfig() *definitions.ExtraConfiguration {
	return &definitions.ExtraConfiguration{
		Identifier:         "import",
		MergeMatchers:      []*labels.Matcher{{Type: labels.MatchEqual, Name: "__imported", Value: "true"}},
		TemplateFiles:      nil,
		AlertmanagerConfig: defaultExtraConfig,
	}
}

const defaultExtraConfig = `
route:
  receiver: receiver1
  routes:
    - receiver: email-receiver
receivers:
  - name: empty receiver
  - name: receiver1
    webhook_configs:
      - url: 'https://webhook.example.com/alerts'
        http_config:
          basic_auth:
            username: 'admin'
            password: 'super-secret-password'
      - url: 'https://slack.com/webhook/ABC123'
        send_resolved: true
  - name: email-receiver
    email_configs:
      - to: 'alerts@example.com'
        from: 'grafana@example.com'
        smarthost: 'smtp.gmail.com:587'
        auth_username: 'grafana@example.com'
        auth_password: 'another-secret-password'
`

type NopTransactionManager struct{}

func newNopTransactionManager() *NopTransactionManager {
	return &NopTransactionManager{}
}

func (n *NopTransactionManager) InTransaction(ctx context.Context, work func(ctx context.Context) error) error {
	return work(context.WithValue(ctx, NopTransactionManager{}, struct{}{}))
}

func assertInTransaction(t *testing.T, ctx context.Context) {
	assert.Truef(t, ctx.Value(NopTransactionManager{}) != nil, "Expected to be executed in transaction but there is none")
}
