package routingtree

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"testing"
	"time"

	"github.com/prometheus/alertmanager/config"
	"github.com/prometheus/alertmanager/pkg/labels"
	"github.com/prometheus/common/model"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"k8s.io/apimachinery/pkg/api/errors"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1"
	"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications/routingtree"

	"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/fakes"
	"github.com/grafana/grafana/pkg/bus"
	"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/accesscontrol/resourcepermissions"
	"github.com/grafana/grafana/pkg/services/dashboards"
	"github.com/grafana/grafana/pkg/services/folder/foldertest"
	"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
	"github.com/grafana/grafana/pkg/services/ngalert/store"
	"github.com/grafana/grafana/pkg/services/org"
	"github.com/grafana/grafana/pkg/tests/api/alerting"
	"github.com/grafana/grafana/pkg/tests/apis"
	"github.com/grafana/grafana/pkg/tests/apis/alerting/notifications/common"
	"github.com/grafana/grafana/pkg/tests/testinfra"
	"github.com/grafana/grafana/pkg/tests/testsuite"
	"github.com/grafana/grafana/pkg/util"
	"github.com/grafana/grafana/pkg/util/testutil"
)

func TestMain(m *testing.M) {
	testsuite.Run(m)
}

func getTestHelper(t *testing.T) *apis.K8sTestHelper {
	return apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{})
}

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

	ctx := context.Background()
	helper := getTestHelper(t)
	client := common.NewRoutingTreeClient(t, helper.Org1.Admin)

	route := &v0alpha1.RoutingTree{
		ObjectMeta: v1.ObjectMeta{
			Namespace: "default",
		},
		Spec: v0alpha1.RoutingTreeSpec{},
	}
	_, err := client.Create(ctx, route, v1.CreateOptions{})
	assert.Error(t, err)
	require.Truef(t, errors.IsMethodNotSupported(err), "Expected MethodNotSupported but got %s", err)

	err = client.Client.DeleteCollection(ctx, v1.DeleteOptions{}, v1.ListOptions{})
	assert.Error(t, err)
	require.Truef(t, errors.IsMethodNotSupported(err), "Expected MethodNotSupported but got %s", err)
}

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

	ctx := context.Background()
	helper := getTestHelper(t)

	org1 := helper.Org1

	type testCase struct {
		user      apis.User
		canRead   bool
		canUpdate bool
	}

	reader := helper.CreateUser("RoutesReader", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
		{
			Actions: []string{
				accesscontrol.ActionAlertingRoutesRead,
			},
		},
	})
	writer := helper.CreateUser("RoutesWriter", "Org1", org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
		{
			Actions: []string{
				accesscontrol.ActionAlertingRoutesRead,
				accesscontrol.ActionAlertingRoutesWrite,
			},
		},
	})
	none := helper.CreateUser("RoutesNone", "Org1", org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{})
	legacyReader := helper.CreateUser("LegacyRoutesReader", "Org1", org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
		{
			Actions: []string{
				accesscontrol.ActionAlertingNotificationsRead,
			},
		},
	})
	legacyWriter := helper.CreateUser("LegacyRoutesWriter", "Org1", org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
		{
			Actions: []string{
				accesscontrol.ActionAlertingNotificationsRead,
				accesscontrol.ActionAlertingNotificationsWrite,
			},
		},
	})

	testCases := []testCase{
		{
			user: none,
		},
		{
			user:      org1.Admin,
			canRead:   true,
			canUpdate: true,
		},
		{
			user:      org1.Editor,
			canRead:   true,
			canUpdate: true,
		},
		{
			user:    org1.Viewer,
			canRead: true,
		},
		{
			user:    reader,
			canRead: true,
		},
		{
			user:      writer,
			canRead:   true,
			canUpdate: true,
		},
		{
			user:    legacyReader,
			canRead: true,
		},
		{
			user:      legacyWriter,
			canRead:   true,
			canUpdate: true,
		},
	}

	admin := org1.Admin
	adminClient := common.NewRoutingTreeClient(t, admin)

	for _, tc := range testCases {
		t.Run(fmt.Sprintf("user '%s'", tc.user.Identity.GetLogin()), func(t *testing.T) {
			client := common.NewRoutingTreeClient(t, tc.user)

			if tc.canRead {
				t.Run("should be able to list routing trees", func(t *testing.T) {
					list, err := client.List(ctx, v1.ListOptions{})
					require.NoError(t, err)
					require.Len(t, list.Items, 1)
					require.Equal(t, v0alpha1.UserDefinedRoutingTreeName, list.Items[0].Name)
				})

				t.Run("should be able to read routing trees by resource identifier", func(t *testing.T) {
					_, err := client.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
					require.NoError(t, err)

					t.Run("should get NotFound if resource does not exist", func(t *testing.T) {
						_, err := client.Get(ctx, "Notfound", v1.GetOptions{})
						require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
					})
				})
			} else {
				t.Run("should be forbidden to list routing trees", func(t *testing.T) {
					_, err := client.List(ctx, v1.ListOptions{})
					require.Error(t, err)
					require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
				})

				t.Run("should be forbidden to read routing tree by name", func(t *testing.T) {
					_, err := client.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
					require.Error(t, err)
					require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)

					t.Run("should get forbidden even if name does not exist", func(t *testing.T) {
						_, err := client.Get(ctx, "Notfound", v1.GetOptions{})
						require.Error(t, err)
						require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
					})
				})
			}

			current, err := adminClient.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
			require.NoError(t, err)
			expected := current.Copy().(*v0alpha1.RoutingTree)
			expected.Spec.Routes = []v0alpha1.RoutingTreeRoute{
				{
					Matchers: []v0alpha1.RoutingTreeMatcher{
						{
							Label: "test",
							Type:  v0alpha1.RoutingTreeMatcherTypeEqual,
							Value: "test",
						},
					},
				},
			}

			d, err := json.Marshal(expected)
			require.NoError(t, err)

			if tc.canUpdate {
				t.Run("should be able to update routing tree", func(t *testing.T) {
					updated, err := client.Update(ctx, expected, v1.UpdateOptions{})
					require.NoErrorf(t, err, "Payload %s", string(d))

					expected = updated

					t.Run("should get NotFound if name does not exist", func(t *testing.T) {
						up := expected.Copy().(*v0alpha1.RoutingTree)
						up.Name = "notFound"
						_, err := client.Update(ctx, up, v1.UpdateOptions{})
						require.Error(t, err)
						require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
					})
				})
			} else {
				t.Run("should be forbidden to update routing tree", func(t *testing.T) {
					_, err := client.Update(ctx, expected, v1.UpdateOptions{})
					require.Error(t, err)
					require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)

					t.Run("should get forbidden even if resource does not exist", func(t *testing.T) {
						up := expected.Copy().(*v0alpha1.RoutingTree)
						up.Name = "notFound"
						_, err := client.Update(ctx, up, v1.UpdateOptions{})
						require.Error(t, err)
						require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
					})
				})
			}

			if tc.canUpdate {
				t.Run("should be able to reset routing tree", func(t *testing.T) {
					err := client.Delete(ctx, expected.Name, v1.DeleteOptions{})
					require.NoError(t, err)

					t.Run("should get NotFound if name does not exist", func(t *testing.T) {
						err := client.Delete(ctx, "notfound", v1.DeleteOptions{})
						require.Error(t, err)
						require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
					})
				})
			} else {
				t.Run("should be forbidden to reset routing tree", func(t *testing.T) {
					err := client.Delete(ctx, expected.Name, v1.DeleteOptions{})
					require.Error(t, err)
					require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)

					t.Run("should be forbidden even if resource does not exist", func(t *testing.T) {
						err := client.Delete(ctx, "notfound", v1.DeleteOptions{})
						require.Error(t, err)
						require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
					})
				})
				require.NoError(t, adminClient.Delete(ctx, expected.Name, v1.DeleteOptions{}))
			}
		})

		err := adminClient.Delete(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.DeleteOptions{})
		require.NoError(t, err)
	}
}

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

	ctx := context.Background()
	helper := getTestHelper(t)

	org := helper.Org1

	admin := org.Admin
	adminClient := common.NewRoutingTreeClient(t, admin)

	env := helper.GetEnv()
	ac := acimpl.ProvideAccessControl(env.FeatureToggles)
	db, err := store.ProvideDBStore(env.Cfg, env.FeatureToggles, env.SQLStore, &foldertest.FakeService{}, &dashboards.FakeDashboardService{}, ac, bus.ProvideBus(tracing.InitializeTracerForTest()))
	require.NoError(t, err)

	current, err := adminClient.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
	require.NoError(t, err)
	require.Equal(t, "none", current.GetProvenanceStatus())

	t.Run("should provide provenance status", func(t *testing.T) {
		require.NoError(t, db.SetProvenance(ctx, &definitions.Route{}, admin.Identity.GetOrgID(), "API"))

		got, err := adminClient.Get(ctx, current.Name, v1.GetOptions{})
		require.NoError(t, err)
		require.Equal(t, "API", got.GetProvenanceStatus())
	})
	t.Run("should not let update if provisioned", func(t *testing.T) {
		updated := current.Copy().(*v0alpha1.RoutingTree)
		updated.Spec.Routes = []v0alpha1.RoutingTreeRoute{
			{
				Matchers: []v0alpha1.RoutingTreeMatcher{
					{
						Label: "test",
						Type:  v0alpha1.RoutingTreeMatcherTypeNotEqual,
						Value: "123",
					},
				},
			},
		}

		_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
		require.Error(t, err)
		require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
	})

	t.Run("should not let delete if provisioned", func(t *testing.T) {
		err := adminClient.Delete(ctx, current.Name, v1.DeleteOptions{})
		require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
	})
}

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

	ctx := context.Background()
	helper := getTestHelper(t)

	adminClient := common.NewRoutingTreeClient(t, helper.Org1.Admin)

	current, err := adminClient.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
	require.NoError(t, err)
	require.NotEmpty(t, current.ResourceVersion)

	t.Run("should forbid if version does not match", func(t *testing.T) {
		updated := current.Copy().(*v0alpha1.RoutingTree)
		updated.ResourceVersion = "test"
		_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
		require.Error(t, err)
		require.Truef(t, errors.IsConflict(err), "should get Forbidden error but got %s", err)
	})
	t.Run("should update if version matches", func(t *testing.T) {
		updated := current.Copy().(*v0alpha1.RoutingTree)
		updated.Spec.Defaults.GroupBy = append(updated.Spec.Defaults.GroupBy, "data")
		actualUpdated, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
		require.NoError(t, err)
		require.EqualValues(t, updated.Spec, actualUpdated.Spec)
		require.NotEqual(t, updated.ResourceVersion, actualUpdated.ResourceVersion)
	})
	t.Run("should update if version is empty", func(t *testing.T) {
		current, err = adminClient.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
		require.NoError(t, err)
		updated := current.Copy().(*v0alpha1.RoutingTree)
		updated.ResourceVersion = ""
		updated.Spec.Routes = append(updated.Spec.Routes, v0alpha1.RoutingTreeRoute{Continue: true})

		actualUpdated, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
		require.NoError(t, err)
		require.EqualValues(t, updated.Spec, actualUpdated.Spec)
		require.NotEqual(t, current.ResourceVersion, actualUpdated.ResourceVersion)
	})
}

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

	ctx := context.Background()
	helper := getTestHelper(t)

	cliCfg := helper.Org1.Admin.NewRestConfig()
	legacyCli := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)

	client := common.NewRoutingTreeClient(t, helper.Org1.Admin)

	receiver := "grafana-default-email"
	timeInterval := "test-time-interval"
	createRoute := func(t *testing.T, route definitions.Route) {
		t.Helper()
		routeClient := common.NewRoutingTreeClient(t, helper.Org1.Admin)
		v1Route, err := routingtree.ConvertToK8sResource(helper.Org1.Admin.Identity.GetOrgID(), route, "", func(int64) string { return "default" })
		require.NoError(t, err)
		_, err = routeClient.Update(ctx, v1Route, v1.UpdateOptions{})
		require.NoError(t, err)
	}

	_, err := common.NewTimeIntervalClient(t, helper.Org1.Admin).Create(ctx, &v0alpha1.TimeInterval{
		ObjectMeta: v1.ObjectMeta{
			Namespace: "default",
		},
		Spec: v0alpha1.TimeIntervalSpec{
			Name:          timeInterval,
			TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(1),
		},
	}, v1.CreateOptions{})
	require.NoError(t, err)

	var regex config.Regexp
	require.NoError(t, json.Unmarshal([]byte(`".*"`), &regex))

	ensureMatcher := func(t *testing.T, mt labels.MatchType, lbl, val string) *labels.Matcher {
		m, err := labels.NewMatcher(mt, lbl, val)
		require.NoError(t, err)
		return m
	}

	t.Run("all matchers are handled", func(t *testing.T) {
		t.Run("can read all legacy matchers", func(t *testing.T) {
			route := definitions.Route{
				Receiver: receiver,
				Routes: []*definitions.Route{
					{
						Match: map[string]string{
							"label_match": "test-123",
						},
						MatchRE: map[string]config.Regexp{
							"label_re": regex,
						},
						Matchers: config.Matchers{
							ensureMatcher(t, labels.MatchRegexp, "label_matchers", "test-321"),
						},
						ObjectMatchers: definitions.ObjectMatchers{
							ensureMatcher(t, labels.MatchNotRegexp, "object-label-matchers", "test-456"),
						},
					},
				},
			}
			createRoute(t, route)
			tree, err := client.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
			require.NoError(t, err)
			expected := []v0alpha1.RoutingTreeMatcher{
				{
					Label: "label_match",
					Type:  v0alpha1.RoutingTreeMatcherTypeEqual,
					Value: "test-123",
				},
				{
					Label: "label_re",
					Type:  v0alpha1.RoutingTreeMatcherTypeEqualRegex,
					Value: ".*",
				},
				{
					Label: "label_matchers",
					Type:  v0alpha1.RoutingTreeMatcherTypeEqualRegex,
					Value: "test-321",
				},
				{
					Label: "object-label-matchers",
					Type:  v0alpha1.RoutingTreeMatcherTypeNotEqualRegex,
					Value: "test-456",
				},
			}
			assert.ElementsMatch(t, expected, tree.Spec.Routes[0].Matchers)
		})
		t.Run("should save into ObjectMatchers", func(t *testing.T) {
			route := definitions.Route{
				Receiver: receiver,
				Routes: []*definitions.Route{
					{
						Match: map[string]string{
							"oldmatch": "123",
						},
					},
					{
						MatchRE: map[string]config.Regexp{
							"oldmatchre": regex,
						},
					},
					{
						Matchers: config.Matchers{
							ensureMatcher(t, labels.MatchNotEqual, "matchers", "v"),
						},
					},
					{
						ObjectMatchers: definitions.ObjectMatchers{
							ensureMatcher(t, labels.MatchEqual, "t2", "v2"),
						},
					},
				},
			}
			createRoute(t, route)
			cfg, _, _ := legacyCli.GetAlertmanagerConfigWithStatus(t)
			expectedRoutes := cfg.AlertmanagerConfig.Route.Routes // autogenerated route is the first one
			expectedRoutes[1].Match = nil
			expectedRoutes[1].ObjectMatchers = definitions.ObjectMatchers{
				ensureMatcher(t, labels.MatchEqual, "oldmatch", "123"),
			}
			expectedRoutes[2].MatchRE = nil
			expectedRoutes[2].ObjectMatchers = definitions.ObjectMatchers{
				ensureMatcher(t, labels.MatchRegexp, "oldmatchre", ".*"),
			}
			expectedRoutes[3].Matchers = nil
			expectedRoutes[3].ObjectMatchers = definitions.ObjectMatchers{
				ensureMatcher(t, labels.MatchNotEqual, "matchers", "v"),
			}

			tree, err := client.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
			require.NoError(t, err)
			_, err = client.Update(ctx, tree, v1.UpdateOptions{})
			require.NoError(t, err)

			cfg, _, _ = legacyCli.GetAlertmanagerConfigWithStatus(t)
			routes := cfg.AlertmanagerConfig.Route.Routes
			require.EqualValues(t, expectedRoutes, routes)
		})
	})

	route := definitions.Route{
		Receiver:       receiver,
		GroupByStr:     []string{"test-123", "test-456"},
		GroupWait:      util.Pointer(model.Duration(30 * time.Second)),
		GroupInterval:  util.Pointer(model.Duration(1 * time.Minute)),
		RepeatInterval: util.Pointer(model.Duration(24 * time.Hour)),
		Routes: []*definitions.Route{
			{
				ObjectMatchers: definitions.ObjectMatchers{
					ensureMatcher(t, labels.MatchNotEqual, "m", "1"),
					ensureMatcher(t, labels.MatchEqual, "n", "1"),
					ensureMatcher(t, labels.MatchRegexp, "o", "1"),
					ensureMatcher(t, labels.MatchNotRegexp, "p", "1"),
				},
				Receiver:            receiver,
				GroupByStr:          []string{"test-789"},
				GroupWait:           util.Pointer(model.Duration(2 * time.Minute)),
				GroupInterval:       util.Pointer(model.Duration(5 * time.Minute)),
				RepeatInterval:      util.Pointer(model.Duration(30 * time.Hour)),
				MuteTimeIntervals:   []string{timeInterval},
				ActiveTimeIntervals: []string{timeInterval},
				Continue:            true,
			},
		},
	}
	createRoute(t, route)

	t.Run("correctly reads all fields", func(t *testing.T) {
		tree, err := client.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
		require.NoError(t, err)
		assert.Equal(t, v0alpha1.RoutingTreeRouteDefaults{
			Receiver:       receiver,
			GroupBy:        []string{"test-123", "test-456"},
			GroupWait:      util.Pointer("30s"),
			GroupInterval:  util.Pointer("1m"),
			RepeatInterval: util.Pointer("1d"),
		}, tree.Spec.Defaults)
		assert.Len(t, tree.Spec.Routes, 1)
		assert.Equal(t, v0alpha1.RoutingTreeRoute{
			Continue:            true,
			Receiver:            util.Pointer(receiver),
			GroupBy:             []string{"test-789"},
			GroupWait:           util.Pointer("2m"),
			GroupInterval:       util.Pointer("5m"),
			RepeatInterval:      util.Pointer("1d6h"),
			MuteTimeIntervals:   []string{timeInterval},
			ActiveTimeIntervals: []string{timeInterval},
			Matchers: []v0alpha1.RoutingTreeMatcher{
				{
					Label: "m",
					Type:  v0alpha1.RoutingTreeMatcherTypeNotEqual,
					Value: "1",
				},
				{
					Label: "n",
					Type:  v0alpha1.RoutingTreeMatcherTypeEqual,
					Value: "1",
				},
				{
					Label: "o",
					Type:  v0alpha1.RoutingTreeMatcherTypeEqualRegex,
					Value: "1",
				},
				{
					Label: "p",
					Type:  v0alpha1.RoutingTreeMatcherTypeNotEqualRegex,
					Value: "1",
				},
			},
		}, tree.Spec.Routes[0])
	})

	t.Run("correctly save all fields", func(t *testing.T) {
		before, status, body := legacyCli.GetAlertmanagerConfigWithStatus(t)
		require.Equalf(t, http.StatusOK, status, body)
		tree, err := client.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
		tree.Spec.Defaults.GroupBy = []string{"test-123", "test-456", "test-789"}
		require.NoError(t, err)
		_, err = client.Update(ctx, tree, v1.UpdateOptions{})
		require.NoError(t, err)

		before.AlertmanagerConfig.Route.GroupByStr = []string{"test-123", "test-456", "test-789"}
		before.AlertmanagerConfig.Route.GroupBy = []model.LabelName{"test-123", "test-456", "test-789"}

		after, status, body := legacyCli.GetAlertmanagerConfigWithStatus(t)
		require.Equalf(t, http.StatusOK, status, body)
		require.Equal(t, before, after)
	})

	t.Run("unicode support in groupBy and matchers", func(t *testing.T) {
		route := definitions.Route{
			Receiver: receiver,
			Routes: []*definitions.Route{
				{
					GroupByStr: []string{"foo🙂"},
					Matchers: config.Matchers{{
						Type:  labels.MatchEqual,
						Name:  "foo🙂",
						Value: "bar",
					}, {
						Type:  labels.MatchNotEqual,
						Name:  "_bar1",
						Value: "baz🙂",
					}, {
						Type:  labels.MatchRegexp,
						Name:  "0baz",
						Value: "[a-zA-Z0-9]+,?",
					}, {
						Type:  labels.MatchNotRegexp,
						Name:  "corge",
						Value: "^[0-9]+((,[0-9]{3})*(,[0-9]{0,3})?)?$",
					}},
					ObjectMatchers: definitions.ObjectMatchers{{
						Type:  labels.MatchEqual,
						Name:  "Προμηθέας", // Prometheus in Greek
						Value: "Prom",
					}, {
						Type:  labels.MatchNotEqual,
						Name:  "犬", // Dog in Japanese (inu)
						Value: "Shiba Inu",
					}},
				},
			},
		}

		createRoute(t, route)
		tree, err := client.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
		require.NoError(t, err)
		assert.Equal(t, "foo🙂", tree.Spec.Routes[0].GroupBy[0])
		expected := []v0alpha1.RoutingTreeMatcher{
			{Label: "foo🙂", Type: v0alpha1.RoutingTreeMatcherTypeEqual, Value: "bar"},
			{Label: "_bar1", Type: v0alpha1.RoutingTreeMatcherTypeNotEqual, Value: "baz🙂"},
			{Label: "0baz", Type: v0alpha1.RoutingTreeMatcherTypeEqualRegex, Value: "[a-zA-Z0-9]+,?"},
			{Label: "corge", Type: v0alpha1.RoutingTreeMatcherTypeNotEqualRegex, Value: "^[0-9]+((,[0-9]{3})*(,[0-9]{0,3})?)?$"},
			{Label: "Προμηθέας", Type: v0alpha1.RoutingTreeMatcherTypeEqual, Value: "Prom"},
			{Label: "犬", Type: v0alpha1.RoutingTreeMatcherTypeNotEqual, Value: "Shiba Inu"},
		}
		assert.ElementsMatch(t, expected, tree.Spec.Routes[0].Matchers)
	})
}

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

	ctx := context.Background()
	helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
		EnableFeatureToggles: []string{"alertingImportAlertmanagerAPI"},
	})

	cliCfg := helper.Org1.Admin.NewRestConfig()
	legacyCli := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)

	client := common.NewRoutingTreeClient(t, helper.Org1.Admin)

	// Now upload a new extra config
	testAlertmanagerConfigYAML := `
route:
  receiver: default

receivers:
- name: default
  webhook_configs:
  - url: 'http://localhost/webhook'
`

	headers := map[string]string{
		"Content-Type":                         "application/yaml",
		"X-Grafana-Alerting-Config-Identifier": "external-system",
		"X-Grafana-Alerting-Merge-Matchers":    "imported=true",
	}

	// Post the configuration to Grafana
	response := legacyCli.ConvertPrometheusPostAlertmanagerConfig(t, definitions.AlertmanagerUserConfig{
		AlertmanagerConfig: testAlertmanagerConfigYAML,
	}, headers)
	require.Equal(t, "success", response.Status)

	current, err := client.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
	require.NoError(t, err)
	updated := current.Copy().(*v0alpha1.RoutingTree)
	updated.Spec.Routes = append(updated.Spec.Routes, v0alpha1.RoutingTreeRoute{
		Matchers: []v0alpha1.RoutingTreeMatcher{
			{
				Label: "imported",
				Type:  v0alpha1.RoutingTreeMatcherTypeEqual,
				Value: "true",
			},
		},
	})

	_, err = client.Update(ctx, updated, v1.UpdateOptions{})
	require.Error(t, err)
	require.Truef(t, errors.IsBadRequest(err), "Should get BadRequest error but got: %s", err)

	// Now delete extra config
	legacyCli.ConvertPrometheusDeleteAlertmanagerConfig(t, headers)

	// and try again
	_, err = client.Update(ctx, updated, v1.UpdateOptions{})
	require.NoError(t, err)
}
