package notifier

import (
	"context"
	"testing"

	amconfig "github.com/prometheus/alertmanager/config"
	"github.com/prometheus/alertmanager/pkg/labels"
	"github.com/stretchr/testify/require"

	"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
)

func TestMultiOrgAlertmanager_SaveAndApplyExtraConfiguration(t *testing.T) {
	orgID := int64(1)

	t.Run("fails when organization does not exist", func(t *testing.T) {
		mam := setupMam(t, nil)
		ctx := context.Background()
		require.NoError(t, mam.LoadAndSyncAlertmanagersForOrgs(ctx))

		extraConfig := definitions.ExtraConfiguration{
			Identifier: "test-config",
			AlertmanagerConfig: `route:
  receiver: test-receiver`,
		}

		err := mam.SaveAndApplyExtraConfiguration(ctx, 999, extraConfig)
		require.Error(t, err)
		require.ErrorContains(t, err, "failed to get current configuration")
	})

	t.Run("save new extra configuration", func(t *testing.T) {
		mam := setupMam(t, nil)
		ctx := context.Background()
		require.NoError(t, mam.LoadAndSyncAlertmanagersForOrgs(ctx))

		extraConfig := definitions.ExtraConfiguration{
			Identifier:    "test-alertmanager-config",
			MergeMatchers: amconfig.Matchers{&labels.Matcher{Type: labels.MatchEqual, Name: "env", Value: "prod"}},
			TemplateFiles: map[string]string{"test.tmpl": "{{ define \"test\" }}Test{{ end }}"},
			AlertmanagerConfig: `route:
  receiver: test-receiver
receivers:
  - name: test-receiver`,
		}

		err := mam.SaveAndApplyExtraConfiguration(ctx, orgID, extraConfig)
		require.NoError(t, err)

		gettableConfig, err := mam.GetAlertmanagerConfiguration(ctx, orgID, false, false)
		require.NoError(t, err)
		require.Len(t, gettableConfig.ExtraConfigs, 1)
		require.Equal(t, extraConfig.Identifier, gettableConfig.ExtraConfigs[0].Identifier)
		require.Equal(t, extraConfig.TemplateFiles, gettableConfig.ExtraConfigs[0].TemplateFiles)

		// Test that we can get the alertmanager config from raw storage
		// We need to pass a decrypt function since the config is now encrypted
		amConfig, err := gettableConfig.ExtraConfigs[0].GetAlertmanagerConfig()
		require.NoError(t, err)
		require.Equal(t, "test-receiver", amConfig.Route.Receiver)
		require.Len(t, amConfig.Receivers, 1)
		require.Equal(t, "test-receiver", amConfig.Receivers[0].Name)
	})

	t.Run("replace existing extra configuration with same identifier", func(t *testing.T) {
		mam := setupMam(t, nil)
		ctx := context.Background()
		require.NoError(t, mam.LoadAndSyncAlertmanagersForOrgs(ctx))

		identifier := "test-config"

		// First add a configuration
		originalConfig := definitions.ExtraConfiguration{
			Identifier:    identifier,
			MergeMatchers: amconfig.Matchers{&labels.Matcher{Type: labels.MatchEqual, Name: "env", Value: "original"}},
			AlertmanagerConfig: `route:
  receiver: original-receiver
receivers:
  - name: original-receiver`,
		}

		err := mam.SaveAndApplyExtraConfiguration(ctx, orgID, originalConfig)
		require.NoError(t, err)

		// Now replace it
		updatedConfig := definitions.ExtraConfiguration{
			Identifier:    identifier,
			MergeMatchers: amconfig.Matchers{&labels.Matcher{Type: labels.MatchEqual, Name: "env", Value: "updated"}},
			TemplateFiles: map[string]string{"updated.tmpl": "{{ define \"updated\" }}Updated{{ end }}"},
			AlertmanagerConfig: `route:
  receiver: updated-receiver
receivers:
  - name: updated-receiver`,
		}

		err = mam.SaveAndApplyExtraConfiguration(ctx, orgID, updatedConfig)
		require.NoError(t, err)

		// Verify only one config exists with updated content
		gettableConfig, err := mam.GetAlertmanagerConfiguration(ctx, orgID, false, false)
		require.NoError(t, err)
		require.Len(t, gettableConfig.ExtraConfigs, 1)
		require.Equal(t, identifier, gettableConfig.ExtraConfigs[0].Identifier)
		require.Contains(t, gettableConfig.ExtraConfigs[0].TemplateFiles, "updated.tmpl")
	})

	t.Run("fail to create multiple extra configurations", func(t *testing.T) {
		mam := setupMam(t, nil)
		ctx := context.Background()
		require.NoError(t, mam.LoadAndSyncAlertmanagersForOrgs(ctx))

		firstConfig := definitions.ExtraConfiguration{
			Identifier:    "first-config",
			MergeMatchers: amconfig.Matchers{&labels.Matcher{Type: labels.MatchEqual, Name: "env", Value: "first"}},
			AlertmanagerConfig: `{
				"route": {
					"receiver": "first-receiver"
				},
				"receivers": [
					{
						"name": "first-receiver"
					}
				]
			}`,
		}

		err := mam.SaveAndApplyExtraConfiguration(ctx, orgID, firstConfig)
		require.NoError(t, err)

		secondConfig := definitions.ExtraConfiguration{
			Identifier:    "second-config",
			MergeMatchers: amconfig.Matchers{&labels.Matcher{Type: labels.MatchEqual, Name: "env", Value: "second"}},
			AlertmanagerConfig: `{
				"route": {
					"receiver": "second-receiver"
				},
				"receivers": [
					{
						"name": "second-receiver"
					}
				]
			}`,
		}

		err = mam.SaveAndApplyExtraConfiguration(ctx, orgID, secondConfig)
		require.Error(t, err)
		require.ErrorContains(t, err, "multiple extra configurations are not supported")
		require.ErrorContains(t, err, "first-config")
	})
}

func TestMultiOrgAlertmanager_SaveAndApplyAlertmanagerConfiguration(t *testing.T) {
	orgID := int64(1)
	ctx := context.Background()

	t.Run("SaveAndApplyAlertmanagerConfiguration preserves existing extra configs", func(t *testing.T) {
		mam := setupMam(t, nil)
		require.NoError(t, mam.LoadAndSyncAlertmanagersForOrgs(ctx))

		extraConfig := definitions.ExtraConfiguration{
			Identifier:    "test-extra-config",
			MergeMatchers: amconfig.Matchers{&labels.Matcher{Type: labels.MatchEqual, Name: "env", Value: "test"}},
			TemplateFiles: map[string]string{"test.tmpl": "{{ define \"test\" }}Test{{ end }}"},
			AlertmanagerConfig: `route:
  receiver: extra-receiver
receivers:
  - name: extra-receiver`,
		}

		err := mam.SaveAndApplyExtraConfiguration(ctx, orgID, extraConfig)
		require.NoError(t, err)

		// Verify extra config was saved
		gettableConfig, err := mam.GetAlertmanagerConfiguration(ctx, orgID, false, false)
		require.NoError(t, err)
		require.Len(t, gettableConfig.ExtraConfigs, 1)
		require.Equal(t, extraConfig.Identifier, gettableConfig.ExtraConfigs[0].Identifier)

		// Apply a new main configuration
		newMainConfig := definitions.PostableUserConfig{
			AlertmanagerConfig: definitions.PostableApiAlertingConfig{
				Config: definitions.Config{
					Route: &definitions.Route{
						Receiver: "main-receiver",
					},
				},
				Receivers: []*definitions.PostableApiReceiver{
					{
						Receiver: amconfig.Receiver{
							Name: "main-receiver",
						},
						PostableGrafanaReceivers: definitions.PostableGrafanaReceivers{
							GrafanaManagedReceivers: []*definitions.PostableGrafanaReceiver{
								{
									Name:     "main-receiver",
									Type:     "email",
									Settings: definitions.RawMessage(`{"addresses": "me@grafana.com"}`),
								},
							},
						},
					},
				},
			},
		}

		err = mam.SaveAndApplyAlertmanagerConfiguration(ctx, orgID, newMainConfig)
		require.NoError(t, err)

		// Verify that the extra config is still present after applying the new main config
		updatedConfig, err := mam.GetAlertmanagerConfiguration(ctx, orgID, false, false)
		require.NoError(t, err)
		require.Len(t, updatedConfig.ExtraConfigs, 1)
		require.Equal(t, extraConfig.Identifier, updatedConfig.ExtraConfigs[0].Identifier)
		require.Equal(t, extraConfig.TemplateFiles, updatedConfig.ExtraConfigs[0].TemplateFiles)

		// Verify the main config was updated
		require.Equal(t, "main-receiver", updatedConfig.AlertmanagerConfig.Route.Receiver)
		require.Len(t, updatedConfig.AlertmanagerConfig.Receivers, 1)
		require.Equal(t, "main-receiver", updatedConfig.AlertmanagerConfig.Receivers[0].Name)
	})

	t.Run("SaveAndApplyAlertmanagerConfiguration handles missing extra_configs field", func(t *testing.T) {
		mam := setupMam(t, nil)
		require.NoError(t, mam.LoadAndSyncAlertmanagersForOrgs(ctx))

		// Apply initial config without extra_configs field
		initialConfig := definitions.PostableUserConfig{
			AlertmanagerConfig: definitions.PostableApiAlertingConfig{
				Config: definitions.Config{
					Route: &definitions.Route{
						Receiver: "initial-receiver",
					},
				},
				Receivers: []*definitions.PostableApiReceiver{
					{
						Receiver: amconfig.Receiver{
							Name: "initial-receiver",
						},
						PostableGrafanaReceivers: definitions.PostableGrafanaReceivers{
							GrafanaManagedReceivers: []*definitions.PostableGrafanaReceiver{
								{
									Name:     "initial-receiver",
									Type:     "email",
									Settings: definitions.RawMessage(`{"addresses": "initial@grafana.com"}`),
								},
							},
						},
					},
				},
			},
		}

		err := mam.SaveAndApplyAlertmanagerConfiguration(ctx, orgID, initialConfig)
		require.NoError(t, err)

		// Apply a new main configuration
		newMainConfig := definitions.PostableUserConfig{
			AlertmanagerConfig: definitions.PostableApiAlertingConfig{
				Config: definitions.Config{
					Route: &definitions.Route{
						Receiver: "main-receiver",
					},
				},
				Receivers: []*definitions.PostableApiReceiver{
					{
						Receiver: amconfig.Receiver{
							Name: "main-receiver",
						},
						PostableGrafanaReceivers: definitions.PostableGrafanaReceivers{
							GrafanaManagedReceivers: []*definitions.PostableGrafanaReceiver{
								{
									Name:     "main-receiver",
									Type:     "email",
									Settings: definitions.RawMessage(`{"addresses": "me@grafana.com"}`),
								},
							},
						},
					},
				},
			},
		}

		err = mam.SaveAndApplyAlertmanagerConfiguration(ctx, orgID, newMainConfig)
		require.NoError(t, err)

		// Verify that no extra configs are present and main config was updated
		updatedConfig, err := mam.GetAlertmanagerConfiguration(ctx, orgID, false, false)
		require.NoError(t, err)
		require.Len(t, updatedConfig.ExtraConfigs, 0)
		require.Equal(t, "main-receiver", updatedConfig.AlertmanagerConfig.Route.Receiver)
	})

	t.Run("SaveAndApplyAlertmanagerConfiguration handles empty extra_configs array", func(t *testing.T) {
		mam := setupMam(t, nil)
		require.NoError(t, mam.LoadAndSyncAlertmanagersForOrgs(ctx))

		// Apply initial config with empty extra_configs
		initialConfig := definitions.PostableUserConfig{
			AlertmanagerConfig: definitions.PostableApiAlertingConfig{
				Config: definitions.Config{
					Route: &definitions.Route{
						Receiver: "initial-receiver",
					},
				},
				Receivers: []*definitions.PostableApiReceiver{
					{
						Receiver: amconfig.Receiver{
							Name: "initial-receiver",
						},
						PostableGrafanaReceivers: definitions.PostableGrafanaReceivers{
							GrafanaManagedReceivers: []*definitions.PostableGrafanaReceiver{
								{
									Name:     "initial-receiver",
									Type:     "email",
									Settings: definitions.RawMessage(`{"addresses": "initial@grafana.com"}`),
								},
							},
						},
					},
				},
			},
			ExtraConfigs: []definitions.ExtraConfiguration{}, // Empty array
		}

		err := mam.SaveAndApplyAlertmanagerConfiguration(ctx, orgID, initialConfig)
		require.NoError(t, err)

		// Apply a new main configuration
		newMainConfig := definitions.PostableUserConfig{
			AlertmanagerConfig: definitions.PostableApiAlertingConfig{
				Config: definitions.Config{
					Route: &definitions.Route{
						Receiver: "main-receiver",
					},
				},
				Receivers: []*definitions.PostableApiReceiver{
					{
						Receiver: amconfig.Receiver{
							Name: "main-receiver",
						},
						PostableGrafanaReceivers: definitions.PostableGrafanaReceivers{
							GrafanaManagedReceivers: []*definitions.PostableGrafanaReceiver{
								{
									Name:     "main-receiver",
									Type:     "email",
									Settings: definitions.RawMessage(`{"addresses": "me@grafana.com"}`),
								},
							},
						},
					},
				},
			},
		}

		err = mam.SaveAndApplyAlertmanagerConfiguration(ctx, orgID, newMainConfig)
		require.NoError(t, err)

		// Verify that no extra configs are present and main config was updated
		updatedConfig, err := mam.GetAlertmanagerConfiguration(ctx, orgID, false, false)
		require.NoError(t, err)
		require.Len(t, updatedConfig.ExtraConfigs, 0)
		require.Equal(t, "main-receiver", updatedConfig.AlertmanagerConfig.Route.Receiver)
	})
}

func TestExtractExtraConfigs(t *testing.T) {
	t.Run("extracts extra configs from JSON", func(t *testing.T) {
		jsonConfig := `{
			"extra_config": [
				{
					"identifier": "test-config",
					"merge_matchers": [],
					"template_files": {"test.tmpl": "test"},
					"alertmanager_config": "route:\n  receiver: test"
				}
			]
		}`

		extraConfigs, err := extractExtraConfigs(jsonConfig)
		require.NoError(t, err)
		require.Len(t, extraConfigs, 1)
		require.Equal(t, "test-config", extraConfigs[0].Identifier)
	})

	t.Run("handles missing extra_config field", func(t *testing.T) {
		jsonConfig := `{"alertmanager_config": {"route": {"receiver": "test"}}}`

		extraConfigs, err := extractExtraConfigs(jsonConfig)
		require.NoError(t, err)
		require.Len(t, extraConfigs, 0)
	})

	t.Run("handles empty extra_config array", func(t *testing.T) {
		jsonConfig := `{"extra_config": []}`

		extraConfigs, err := extractExtraConfigs(jsonConfig)
		require.NoError(t, err)
		require.Len(t, extraConfigs, 0)
	})

	t.Run("handles null extra_config", func(t *testing.T) {
		jsonConfig := `{"extra_config": null}`

		extraConfigs, err := extractExtraConfigs(jsonConfig)
		require.NoError(t, err)
		require.Len(t, extraConfigs, 0)
	})
}

func TestMultiOrgAlertmanager_DeleteExtraConfiguration(t *testing.T) {
	orgID := int64(1)

	t.Run("successfully delete existing extra configuration", func(t *testing.T) {
		mam := setupMam(t, nil)
		ctx := context.Background()
		require.NoError(t, mam.LoadAndSyncAlertmanagersForOrgs(ctx))

		identifier := "test-identifier"

		extraConfig := definitions.ExtraConfiguration{
			Identifier:    identifier,
			MergeMatchers: amconfig.Matchers{&labels.Matcher{Type: labels.MatchEqual, Name: "env", Value: "delete"}},
			AlertmanagerConfig: `route:
  receiver: test-receiver
receivers:
  - name: test-receiver`,
		}

		err := mam.SaveAndApplyExtraConfiguration(ctx, orgID, extraConfig)
		require.NoError(t, err)

		gettableConfig, err := mam.GetAlertmanagerConfiguration(ctx, orgID, false, false)
		require.NoError(t, err)
		require.Len(t, gettableConfig.ExtraConfigs, 1)

		err = mam.DeleteExtraConfiguration(ctx, orgID, identifier)
		require.NoError(t, err)

		gettableConfig, err = mam.GetAlertmanagerConfiguration(ctx, orgID, false, false)
		require.NoError(t, err)
		require.Len(t, gettableConfig.ExtraConfigs, 0)
	})

	t.Run("deletion of non-existent configuration", func(t *testing.T) {
		mam := setupMam(t, nil)
		ctx := context.Background()
		require.NoError(t, mam.LoadAndSyncAlertmanagersForOrgs(ctx))

		err := mam.DeleteExtraConfiguration(ctx, orgID, "non-existent")
		require.NoError(t, err)
	})

	t.Run("deletion in non-existent org fails", func(t *testing.T) {
		mam := setupMam(t, nil)
		ctx := context.Background()

		err := mam.DeleteExtraConfiguration(ctx, 999, "test-config")
		require.Error(t, err)
		require.ErrorContains(t, err, "failed to get current configuration")
	})
}
