package notifier

import (
	"context"
	"encoding/json"
	"testing"
	"time"

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

	"github.com/grafana/grafana/pkg/infra/db"
	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/services/dashboards"
	"github.com/grafana/grafana/pkg/services/featuremgmt"
	"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
	"github.com/grafana/grafana/pkg/services/ngalert/metrics"
	"github.com/grafana/grafana/pkg/services/ngalert/store"
	"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
	"github.com/grafana/grafana/pkg/services/secrets/database"
	secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/grafana/grafana/pkg/tests/testsuite"
	"github.com/grafana/grafana/pkg/util/testutil"
)

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

func setupAMTest(t *testing.T) *alertmanager {
	dir := t.TempDir()
	cfg := &setting.Cfg{
		DataPath: dir,
		AppURL:   "http://localhost:9093",
	}

	l := log.New("alertmanager-test")

	m := metrics.NewAlertmanagerMetrics(prometheus.NewRegistry(), l)
	sqlStore := db.InitTestDB(t)
	s := &store.DBstore{
		Cfg: setting.UnifiedAlertingSettings{
			BaseInterval:                  10 * time.Second,
			DefaultRuleEvaluationInterval: time.Minute,
		},
		SQLStore:         sqlStore,
		Logger:           l,
		DashboardService: dashboards.NewFakeDashboardService(t),
	}

	kvStore := fakes.NewFakeKVStore(t)
	secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
	decryptFn := secretsService.GetDecryptedValue

	orgID := 1
	stateStore := NewFileStore(int64(orgID), kvStore)
	crypto := NewCrypto(secretsService, s, l)

	am, err := NewAlertmanager(context.Background(), 1, cfg, s, stateStore, &NilPeer{}, decryptFn, nil, m, featuremgmt.WithFeatures(), crypto, nil)
	require.NoError(t, err)
	return am
}

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

	am := setupAMTest(t)
	require.False(t, am.Ready())
}

func TestAlertmanager_SaveAndApplyConfig_WithExternalSecrets(t *testing.T) {
	am := setupAMTest(t)

	cfg := &definitions.PostableUserConfig{
		AlertmanagerConfig: definitions.PostableApiAlertingConfig{
			Config: definitions.Config{
				Route: &definitions.Route{
					Receiver: "default-receiver",
				},
			},
			Receivers: []*definitions.PostableApiReceiver{
				{
					Receiver: config.Receiver{Name: "default-receiver"},
				},
			},
		},
		ExtraConfigs: []definitions.ExtraConfiguration{
			{
				Identifier:    "external-prometheus",
				MergeMatchers: []*labels.Matcher{{Type: labels.MatchEqual, Name: "cluster", Value: "prod"}},
				AlertmanagerConfig: `
route:
  receiver: webhook-receiver
receivers:
  - name: webhook-receiver
    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'`,
			},
		},
	}

	err := am.SaveAndApplyConfig(context.Background(), cfg)
	require.NoError(t, err)

	savedConfig, err := am.Store.GetLatestAlertmanagerConfiguration(context.Background(), am.Base.TenantID())
	require.NoError(t, err)

	// Verify secrets are encrypted in stored config
	var savedUserConfig definitions.PostableUserConfig
	err = json.Unmarshal([]byte(savedConfig.AlertmanagerConfiguration), &savedUserConfig)
	require.NoError(t, err)

	require.Len(t, savedUserConfig.ExtraConfigs, 1)
	extraConfig := savedUserConfig.ExtraConfigs[0]

	require.Equal(t, "external-prometheus", extraConfig.Identifier)
	require.NotContains(t, extraConfig.AlertmanagerConfig, "super-secret-password")
	require.NotContains(t, extraConfig.AlertmanagerConfig, "another-secret-password")
	require.NotContains(t, extraConfig.AlertmanagerConfig, "ABC123")

	// Apply the saved configuration again and check that it is applied without errors
	err = am.ApplyConfig(context.Background(), savedConfig)
	require.NoError(t, err)
	require.True(t, am.Ready())
}

func TestAlertmanager_ApplyConfig(t *testing.T) {
	basicConfig := func() definitions.PostableApiAlertingConfig {
		return definitions.PostableApiAlertingConfig{
			Config: definitions.Config{
				Route: &definitions.Route{
					Receiver: "default-receiver",
					ObjectMatchers: definitions.ObjectMatchers{
						&labels.Matcher{
							Type:  labels.MatchEqual,
							Name:  "__grafana_autogenerated__",
							Value: "true",
						},
					},
				},
			},
			Receivers: []*definitions.PostableApiReceiver{
				{
					Receiver: config.Receiver{
						Name: "default-receiver",
					},
				},
			},
		}
	}

	testCases := []struct {
		name          string
		config        *definitions.PostableUserConfig
		expectedError string
		skipInvalid   bool
	}{
		{
			name: "basic config",
			config: &definitions.PostableUserConfig{
				AlertmanagerConfig: basicConfig(),
				TemplateFiles: map[string]string{
					"grafana-template": "{{ define \"grafana.title\" }}Alert{{ end }}",
				},
			},
			skipInvalid: false,
		},
		{
			name: "with mimir config",
			config: &definitions.PostableUserConfig{
				AlertmanagerConfig: basicConfig(),
				TemplateFiles: map[string]string{
					"grafana-template": "{{ define \"grafana.title\" }}Grafana Alert{{ end }}",
				},
				ExtraConfigs: []definitions.ExtraConfiguration{
					{
						Identifier: "mimir-prod",
						MergeMatchers: config.Matchers{
							{
								Type:  labels.MatchEqual,
								Name:  "__mimir__",
								Value: "true",
							},
						},
						TemplateFiles: map[string]string{
							"mimir-template": "{{ define \"mimir.title\" }}Mimir Alert{{ end }}",
						},
						AlertmanagerConfig: `route:
  receiver: mimir-webhook
  group_by:
    - alertname
    - cluster
receivers:
  - name: mimir-webhook
    webhook_configs:
      - url: https://webhook.example.com/alerts
        send_resolved: true
        http_config: {}`,
					},
				},
			},
			skipInvalid: false,
		},
		{
			name: "invalid config fails",
			config: &definitions.PostableUserConfig{
				AlertmanagerConfig: basicConfig(),
				ExtraConfigs: []definitions.ExtraConfiguration{
					{
						Identifier:    "", // invalid: empty identifier
						MergeMatchers: config.Matchers{},
						AlertmanagerConfig: `route:
  receiver: test-receiver
receivers:
  - name: test-receiver`,
					},
				},
			},
			expectedError: "failed to get full alertmanager configuration",
			skipInvalid:   false,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			am := setupAMTest(t)
			ctx := context.Background()

			err := am.SaveAndApplyConfig(ctx, tc.config)

			if tc.expectedError != "" {
				require.Error(t, err)
				require.ErrorContains(t, err, tc.expectedError)
			} else {
				require.NoError(t, err)

				templateDefs := tc.config.GetMergedTemplateDefinitions()
				expectedTemplateCount := len(tc.config.TemplateFiles)
				if len(tc.config.ExtraConfigs) > 0 {
					expectedTemplateCount += len(tc.config.ExtraConfigs[0].TemplateFiles)
				}
				require.Len(t, templateDefs, expectedTemplateCount)
			}
		})
	}
}
