package pluginconfig

import (
	"context"
	"os"
	"strings"
	"testing"

	"gopkg.in/ini.v1"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/grafana/grafana-azure-sdk-go/v2/azsettings"

	"github.com/grafana/grafana/pkg/plugins"
	"github.com/grafana/grafana/pkg/plugins/auth"
	"github.com/grafana/grafana/pkg/plugins/config"
	"github.com/grafana/grafana/pkg/plugins/envvars"
	"github.com/grafana/grafana/pkg/plugins/manager/pluginfakes"
	"github.com/grafana/grafana/pkg/services/featuremgmt"
	"github.com/grafana/grafana/pkg/setting"
)

func TestPluginEnvVarsProvider_PluginEnvVars(t *testing.T) {
	t.Run("backend datasource with license", func(t *testing.T) {
		p := &plugins.Plugin{
			JSONData: plugins.JSONData{
				ID: "test",
			},
		}

		licensing := &pluginfakes.FakeLicensingService{
			LicenseEdition: "test",
			TokenRaw:       "token",
			LicensePath:    "/path/to/ent/license",
			LicenseAppURL:  "https://myorg.com/",
		}

		cfg := &PluginInstanceCfg{
			PluginSettings: map[string]map[string]string{
				"test": {
					"custom_env_var": "customVal",
				},
			},
			AWSAssumeRoleEnabled: true,
			Features:             featuremgmt.WithFeatures(),
		}

		provider := NewEnvVarsProvider(cfg, licensing, &fakeSSOSettingsProvider{})
		envVars := provider.PluginEnvVars(context.Background(), p)
		assert.Len(t, envVars, 6)
		assert.Equal(t, "GF_VERSION=", envVars[0])
		assert.Equal(t, "GF_EDITION=test", envVars[1])
		assert.Equal(t, "GF_ENTERPRISE_LICENSE_PATH=/path/to/ent/license", envVars[2])
		assert.Equal(t, "GF_ENTERPRISE_APP_URL=https://myorg.com/", envVars[3])
		assert.Equal(t, "GF_ENTERPRISE_LICENSE_TEXT=token", envVars[4])
		assert.Equal(t, "GF_PLUGIN_CUSTOM_ENV_VAR=customVal", envVars[5])
	})
}

func TestPluginEnvVarsProvider_skipHostEnvVars(t *testing.T) {
	const (
		envVarName  = "HTTP_PROXY"
		envVarValue = "lorem ipsum"
	)

	t.Setenv(envVarName, envVarValue)

	p := &plugins.Plugin{
		JSONData: plugins.JSONData{
			ID: "test",
		},
	}

	t.Run("without FlagPluginsSkipHostEnvVars should not populate host env vars", func(t *testing.T) {
		cfg := setting.NewCfg()
		pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures())
		require.NoError(t, err)

		provider := NewEnvVarsProvider(pCfg, nil, &fakeSSOSettingsProvider{})
		envVars := provider.PluginEnvVars(context.Background(), p)

		// We want to test that the envvars.Provider does not add any of the host env vars.
		// When starting the plugin via go-plugin, ALL host env vars will be added by go-plugin,
		// but we are testing the envvars.Provider here, so that's outside the scope of this test.
		_, ok := getEnvVarWithExists(envVars, envVarName)
		require.False(t, ok, "host env var should not be present")
	})

	t.Run("with SkipHostEnvVars = true", func(t *testing.T) {
		p.SkipHostEnvVars = true

		cfg := setting.NewCfg()
		pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures())
		require.NoError(t, err)
		provider := NewEnvVarsProvider(pCfg, nil, &fakeSSOSettingsProvider{})

		t.Run("should populate allowed host env vars", func(t *testing.T) {
			// Set all allowed variables
			for _, ev := range envvars.PermittedHostEnvVarNames() {
				t.Setenv(ev, envVarValue)
			}
			envVars := provider.PluginEnvVars(context.Background(), p)

			// Test against each variable
			for _, expEvName := range envvars.PermittedHostEnvVarNames() {
				gotEvValue, ok := getEnvVarWithExists(envVars, expEvName)
				require.True(t, ok, "host env var should be present")
				require.Equal(t, envVarValue, gotEvValue)
			}
		})

		t.Run("should not populate host env vars that aren't allowed", func(t *testing.T) {
			// Set all allowed variables
			for _, ev := range envvars.PermittedHostEnvVarNames() {
				t.Setenv(ev, envVarValue)
			}
			// ...and an extra one, which should not leak
			const superSecretEnvVariableName = "SUPER_SECRET_VALUE"
			t.Setenv(superSecretEnvVariableName, "01189998819991197253")
			envVars := provider.PluginEnvVars(context.Background(), p)

			// Super secret should not leak
			_, ok := getEnvVarWithExists(envVars, superSecretEnvVariableName)
			require.False(t, ok, "super secret env var should not be leaked")

			// Everything else should be present
			for _, expEvName := range envvars.PermittedHostEnvVarNames() {
				var gotEvValue string
				gotEvValue, ok = getEnvVarWithExists(envVars, expEvName)
				require.True(t, ok, "host env var should be present")
				require.Equal(t, envVarValue, gotEvValue)
			}
		})
	})
}

func TestPluginEnvVarsProvider_tracingEnvironmentVariables(t *testing.T) {
	const pluginID = "plugin_id"

	defaultPlugin := &plugins.Plugin{
		JSONData: plugins.JSONData{
			ID:   pluginID,
			Info: plugins.Info{Version: "1.0.0"},
		},
	}
	pluginWithoutVersion := &plugins.Plugin{
		JSONData: plugins.JSONData{ID: pluginID},
	}

	defaultOTelCfg := config.OpenTelemetryCfg{
		Address:     "127.0.0.1:4317",
		Propagation: "",
	}

	expDefaultOtlp := func(t *testing.T, envVars []string) {
		found := map[string]bool{
			"address":        false,
			"plugin_version": false,
			"propagation":    false,
		}
		setFound := func(v string) {
			require.False(t, found[v], "duplicate env var found")
			found[v] = true
		}
		for _, v := range envVars {
			switch v {
			case "GF_PLUGIN_VERSION=1.0.0":
				setFound("plugin_version")
			case "GF_INSTANCE_OTLP_ADDRESS=127.0.0.1:4317":
				setFound("address")
			case "GF_INSTANCE_OTLP_PROPAGATION=":
				setFound("propagation")
			}
		}
		for k, f := range found {
			require.Truef(t, f, "%q env var not found: %+v", k, envVars)
		}
	}
	expNoTracing := func(t *testing.T, envVars []string) {
		for _, v := range envVars {
			assert.False(t, strings.HasPrefix(v, "GF_TRACING"), "should not have tracing env var")
			assert.False(
				t,
				strings.HasPrefix(v, "GF_PLUGIN_VERSION"),
				"GF_PLUGIN_VERSION is tracing-only and should not be present when tracing is disabled",
			)
		}
	}
	expGfPluginVersionNotPresent := func(t *testing.T, envVars []string) {
		for _, e := range envVars {
			assert.False(t, strings.HasPrefix("GF_PLUGIN_VERSION=", e), "GF_PLUGIN_VERSION shouldn't be present")
		}
	}
	expGfPluginVersionPresent := func(t *testing.T, envVars []string) {
		var found bool
		for _, e := range envVars {
			if e != "GF_PLUGIN_VERSION=1.0.0" {
				continue
			}
			assert.False(t, found, "GF_PLUGIN_VERSION is present multiple times")
			found = true
		}
		assert.Truef(t, found, "GF_PLUGIN_VERSION is not present: %+v", envVars)
	}

	for _, tc := range []struct {
		name   string
		cfg    *PluginInstanceCfg
		plugin *plugins.Plugin
		exp    func(t *testing.T, envVars []string)
	}{
		{
			name: "otel not configured",
			cfg: &PluginInstanceCfg{
				AWSAssumeRoleEnabled: false,
				Tracing:              config.Tracing{},
			},
			plugin: defaultPlugin,
			exp:    expNoTracing,
		},
		{
			name: "otlp no propagation",
			cfg: &PluginInstanceCfg{
				Tracing: config.Tracing{
					OpenTelemetry: defaultOTelCfg,
				},
			},

			plugin: defaultPlugin,
			exp:    expDefaultOtlp,
		},
		{
			name: "otlp propagation plugin enabled",
			cfg: &PluginInstanceCfg{
				AWSAssumeRoleEnabled: true,
				Tracing: config.Tracing{
					OpenTelemetry: config.OpenTelemetryCfg{
						Address:     "127.0.0.1:4317",
						Propagation: "w3c",

						// Sensible default values for the sampler set by pkg/infra/tracing while reading config.ini
						Sampler:          "",
						SamplerParam:     1.0,
						SamplerRemoteURL: "",
					},
				},
			},
			plugin: defaultPlugin,
			exp: func(t *testing.T, envVars []string) {
				assert.Len(t, envVars, 7)
				assert.Equal(t, "GF_VERSION=", envVars[0])
				assert.Equal(t, "GF_INSTANCE_OTLP_ADDRESS=127.0.0.1:4317", envVars[1])
				assert.Equal(t, "GF_INSTANCE_OTLP_PROPAGATION=w3c", envVars[2])
				assert.Equal(t, "GF_INSTANCE_OTLP_SAMPLER_TYPE=", envVars[3])
				assert.Equal(t, "GF_INSTANCE_OTLP_SAMPLER_PARAM=1.000000", envVars[4])
				assert.Equal(t, "GF_INSTANCE_OTLP_SAMPLER_REMOTE_URL=", envVars[5])
				assert.Equal(t, "GF_PLUGIN_VERSION=1.0.0", envVars[6])
			},
		},
		{
			name: "otlp enabled composite propagation",
			cfg: &PluginInstanceCfg{
				AWSAssumeRoleEnabled: true,
				Tracing: config.Tracing{
					OpenTelemetry: config.OpenTelemetryCfg{
						Address:     "127.0.0.1:4317",
						Propagation: "w3c,jaeger",

						// Sensible default values for the sampler set by pkg/infra/tracing while reading config.ini
						Sampler:          "",
						SamplerParam:     1.0,
						SamplerRemoteURL: "",
					},
				},
			},
			plugin: defaultPlugin,
			exp: func(t *testing.T, envVars []string) {
				assert.Len(t, envVars, 7)
				assert.Equal(t, "GF_VERSION=", envVars[0])
				assert.Equal(t, "GF_INSTANCE_OTLP_ADDRESS=127.0.0.1:4317", envVars[1])
				assert.Equal(t, "GF_INSTANCE_OTLP_PROPAGATION=w3c,jaeger", envVars[2])
				assert.Equal(t, "GF_INSTANCE_OTLP_SAMPLER_TYPE=", envVars[3])
				assert.Equal(t, "GF_INSTANCE_OTLP_SAMPLER_PARAM=1.000000", envVars[4])
				assert.Equal(t, "GF_INSTANCE_OTLP_SAMPLER_REMOTE_URL=", envVars[5])
				assert.Equal(t, "GF_PLUGIN_VERSION=1.0.0", envVars[6])
			},
		},
		{
			name: "GF_PLUGIN_VERSION is not present if tracing is disabled",
			cfg: &PluginInstanceCfg{
				Tracing: config.Tracing{
					OpenTelemetry: config.OpenTelemetryCfg{},
				},
			},
			plugin: defaultPlugin,
			exp:    expGfPluginVersionNotPresent,
		},
		{
			name: "GF_PLUGIN_VERSION is present if tracing is enabled and plugin has version",
			cfg: &PluginInstanceCfg{
				Tracing: config.Tracing{
					OpenTelemetry: defaultOTelCfg,
				},
			},
			plugin: defaultPlugin,
			exp:    expGfPluginVersionPresent,
		},
		{
			name: "GF_PLUGIN_VERSION is not present if tracing is enabled but plugin doesn't have a version",
			cfg: &PluginInstanceCfg{
				Tracing: config.Tracing{
					OpenTelemetry: config.OpenTelemetryCfg{},
				},
			},
			plugin: pluginWithoutVersion,
			exp:    expGfPluginVersionNotPresent,
		},
		{
			name: "no sampling (neversample)",
			cfg: &PluginInstanceCfg{
				Tracing: config.Tracing{
					OpenTelemetry: config.OpenTelemetryCfg{
						Address:          "127.0.0.1:4317",
						Propagation:      "jaeger",
						Sampler:          "",
						SamplerParam:     0.0,
						SamplerRemoteURL: "",
					},
				},
			},
			plugin: defaultPlugin,
			exp: func(t *testing.T, envVars []string) {
				require.Empty(t, getEnvVar(envVars, "GF_INSTANCE_OTLP_SAMPLER_TYPE"))
				require.Equal(t, "0.000000", getEnvVar(envVars, "GF_INSTANCE_OTLP_SAMPLER_PARAM"))
				require.Empty(t, getEnvVar(envVars, "GF_INSTANCE_OTLP_SAMPLER_REMOTE_URL"))
			},
		},
		{
			name: "empty sampler with param",
			cfg: &PluginInstanceCfg{
				Tracing: config.Tracing{
					OpenTelemetry: config.OpenTelemetryCfg{
						Address:          "127.0.0.1:4317",
						Propagation:      "jaeger",
						Sampler:          "",
						SamplerParam:     0.5,
						SamplerRemoteURL: "",
					},
				},
			},
			plugin: defaultPlugin,
			exp: func(t *testing.T, envVars []string) {
				require.Equal(t, "", getEnvVar(envVars, "GF_INSTANCE_OTLP_SAMPLER_TYPE"))
				require.Equal(t, "0.500000", getEnvVar(envVars, "GF_INSTANCE_OTLP_SAMPLER_PARAM"))
				require.Equal(t, "", getEnvVar(envVars, "GF_INSTANCE_OTLP_SAMPLER_REMOTE_URL"))
			},
		},
		{
			name: "const sampler with param",
			cfg: &PluginInstanceCfg{
				Tracing: config.Tracing{
					OpenTelemetry: config.OpenTelemetryCfg{
						Address:          "127.0.0.1:4317",
						Propagation:      "jaeger",
						Sampler:          "const",
						SamplerParam:     0.5,
						SamplerRemoteURL: "",
					},
				},
			},
			plugin: defaultPlugin,
			exp: func(t *testing.T, envVars []string) {
				require.Equal(t, "const", getEnvVar(envVars, "GF_INSTANCE_OTLP_SAMPLER_TYPE"))
				require.Equal(t, "0.500000", getEnvVar(envVars, "GF_INSTANCE_OTLP_SAMPLER_PARAM"))
				require.Equal(t, "", getEnvVar(envVars, "GF_INSTANCE_OTLP_SAMPLER_REMOTE_URL"))
			},
		},
		{
			name: "rateLimiting sampler",
			cfg: &PluginInstanceCfg{
				Tracing: config.Tracing{
					OpenTelemetry: config.OpenTelemetryCfg{
						Address:          "127.0.0.1:4317",
						Propagation:      "jaeger",
						Sampler:          "rateLimiting",
						SamplerParam:     0.5,
						SamplerRemoteURL: "",
					},
				},
			},
			plugin: defaultPlugin,
			exp: func(t *testing.T, envVars []string) {
				require.Equal(t, "rateLimiting", getEnvVar(envVars, "GF_INSTANCE_OTLP_SAMPLER_TYPE"))
				require.Equal(t, "0.500000", getEnvVar(envVars, "GF_INSTANCE_OTLP_SAMPLER_PARAM"))
				require.Equal(t, "", getEnvVar(envVars, "GF_INSTANCE_OTLP_SAMPLER_REMOTE_URL"))
			},
		},
		{
			name: "remote sampler",
			cfg: &PluginInstanceCfg{
				Tracing: config.Tracing{
					OpenTelemetry: config.OpenTelemetryCfg{
						Address:          "127.0.0.1:4317",
						Propagation:      "jaeger",
						Sampler:          "remote",
						SamplerParam:     0.5,
						SamplerRemoteURL: "127.0.0.1:10001",
					},
				},
			},
			plugin: defaultPlugin,
			exp: func(t *testing.T, envVars []string) {
				require.Equal(t, "remote", getEnvVar(envVars, "GF_INSTANCE_OTLP_SAMPLER_TYPE"))
				require.Equal(t, "0.500000", getEnvVar(envVars, "GF_INSTANCE_OTLP_SAMPLER_PARAM"))
				require.Equal(t, "127.0.0.1:10001", getEnvVar(envVars, "GF_INSTANCE_OTLP_SAMPLER_REMOTE_URL"))
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			p := NewEnvVarsProvider(tc.cfg, nil, &fakeSSOSettingsProvider{})
			envVars := p.PluginEnvVars(context.Background(), tc.plugin)
			tc.exp(t, envVars)
		})
	}
}

// getEnvVarWithExists takes a slice of strings in this format: "K=V" (env vars), and returns the "V" where K = wanted.
// If there's no such key, it returns false as the second argument.
func getEnvVarWithExists(vars []string, wanted string) (string, bool) {
	for _, v := range vars {
		parts := strings.SplitN(v, "=", 2)
		if parts[0] != wanted {
			continue
		}
		var r string
		if len(parts) < 2 {
			r = ""
		} else {
			r = parts[1]
		}
		return r, true
	}
	return "", false
}

// getEnvVar is like getEnvVarWithExists, but it returns just one string, without the boolean "ok" value.
// If the wanted environment variable does not exist, it returns an empty string.
func getEnvVar(vars []string, wanted string) string {
	v, _ := getEnvVarWithExists(vars, wanted)
	return v
}

func TestPluginEnvVarsProvider_authEnvVars(t *testing.T) {
	t.Run("backend datasource with auth registration", func(t *testing.T) {
		p := &plugins.Plugin{
			JSONData: plugins.JSONData{
				ID:  "test",
				IAM: &auth.IAM{},
			},
			ExternalService: &auth.ExternalService{
				ClientID:     "clientID",
				ClientSecret: "clientSecret",
				PrivateKey:   "privatePem",
			},
		}

		cfg := &setting.Cfg{
			Raw:    ini.Empty(),
			AppURL: "https://myorg.com/",
		}

		pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures())
		require.NoError(t, err)

		provider := NewEnvVarsProvider(pCfg, nil, &fakeSSOSettingsProvider{})
		envVars := provider.PluginEnvVars(context.Background(), p)
		assert.Equal(t, "GF_VERSION=", envVars[0])
		assert.Equal(t, "GF_APP_URL=https://myorg.com/", envVars[1])
		assert.Equal(t, "GF_PLUGIN_APP_CLIENT_ID=clientID", envVars[2])
		assert.Equal(t, "GF_PLUGIN_APP_CLIENT_SECRET=clientSecret", envVars[3])
		assert.Equal(t, "GF_PLUGIN_APP_PRIVATE_KEY=privatePem", envVars[4])
	})
}

func TestPluginEnvVarsProvider_awsEnvVars(t *testing.T) {
	tcs := []struct {
		name             string
		pluginID         string
		forwardToPlugins []string
		hostEnvVars      map[string]string
		expected         []string
		unexpectedKeys   []string
	}{
		{
			name:             "generates AWS auth settings for whitelisted plugin",
			forwardToPlugins: []string{"foobar-datasource", "cloudwatch", "prometheus"},
			pluginID:         "cloudwatch",
			expected:         []string{"GF_VERSION=", "AWS_AUTH_AssumeRoleEnabled=false", "AWS_AUTH_AllowedAuthProviders=grafana_assume_role,keys", "AWS_AUTH_EXTERNAL_ID=mock_external_id", "AWS_AUTH_SESSION_DURATION=10m", "AWS_CW_LIST_METRICS_PAGE_LIMIT=100"},
		},
		{
			name:             "does not generate AWS env vars for non-whitelisted plugin",
			forwardToPlugins: []string{"cloudwatch", "foobar-datasource"},
			pluginID:         "prometheus",
			expected:         []string{"GF_VERSION="},
		},
		{
			name:             "forwards AWS SDK credential chain env vars for whitelisted plugin",
			forwardToPlugins: []string{"cloudwatch"},
			pluginID:         "cloudwatch",
			hostEnvVars: map[string]string{
				"AWS_ROLE_ARN":                           "arn:aws:iam::123456789012:role/test-role",
				"AWS_WEB_IDENTITY_TOKEN_FILE":            "/var/run/secrets/token",
				"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI": "/v2/credentials/uuid",
				"AWS_REGION":                             "us-east-1",
			},
			expected: []string{
				"GF_VERSION=",
				"AWS_AUTH_AssumeRoleEnabled=false", "AWS_AUTH_AllowedAuthProviders=grafana_assume_role,keys",
				"AWS_AUTH_EXTERNAL_ID=mock_external_id", "AWS_AUTH_SESSION_DURATION=10m", "AWS_CW_LIST_METRICS_PAGE_LIMIT=100",
				"AWS_ROLE_ARN=arn:aws:iam::123456789012:role/test-role",
				"AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/token",
				"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=/v2/credentials/uuid",
				"AWS_REGION=us-east-1",
			},
		},
		{
			name:             "does not forward AWS SDK credential chain env vars for non-whitelisted plugin",
			forwardToPlugins: []string{"cloudwatch"},
			pluginID:         "some-other-plugin",
			hostEnvVars: map[string]string{
				"AWS_ROLE_ARN": "arn:aws:iam::123456789012:role/test-role",
				"AWS_REGION":   "us-east-1",
			},
			expected:       []string{"GF_VERSION="},
			unexpectedKeys: []string{"AWS_ROLE_ARN", "AWS_REGION"},
		},
		{
			name:             "only forwards AWS SDK env vars that are set in the host environment",
			forwardToPlugins: []string{"cloudwatch"},
			pluginID:         "cloudwatch",
			hostEnvVars: map[string]string{
				"AWS_REGION": "eu-west-1",
			},
			expected: []string{
				"GF_VERSION=",
				"AWS_AUTH_AssumeRoleEnabled=false", "AWS_AUTH_AllowedAuthProviders=grafana_assume_role,keys",
				"AWS_AUTH_EXTERNAL_ID=mock_external_id", "AWS_AUTH_SESSION_DURATION=10m", "AWS_CW_LIST_METRICS_PAGE_LIMIT=100",
				"AWS_REGION=eu-west-1",
			},
			unexpectedKeys: []string{"AWS_ROLE_ARN", "AWS_WEB_IDENTITY_TOKEN_FILE", "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"},
		},
	}

	for _, tc := range tcs {
		t.Run(tc.name, func(t *testing.T) {
			// Clear any pre-existing AWS host env vars (e.g., from CI) that aren't
			// explicitly set by this test case, so they don't leak into results.
			for _, envVarName := range awsHostEnvVarNames {
				if _, ok := tc.hostEnvVars[envVarName]; !ok {
					t.Setenv(envVarName, "")
					require.NoError(t, os.Unsetenv(envVarName))
				}
			}

			for k, v := range tc.hostEnvVars {
				t.Setenv(k, v)
			}

			p := &plugins.Plugin{
				JSONData: plugins.JSONData{
					ID: tc.pluginID,
				},
			}
			cfg := &PluginInstanceCfg{
				AWSAssumeRoleEnabled:      false,
				AWSAllowedAuthProviders:   []string{"grafana_assume_role", "keys"},
				AWSExternalId:             "mock_external_id",
				AWSSessionDuration:        "10m",
				AWSListMetricsPageLimit:   "100",
				AWSForwardSettingsPlugins: tc.forwardToPlugins,
				Features:                  featuremgmt.WithFeatures(),
			}

			provider := NewEnvVarsProvider(cfg, nil, &fakeSSOSettingsProvider{})
			envVars := provider.PluginEnvVars(context.Background(), p)
			assert.ElementsMatch(t, tc.expected, envVars)

			for _, key := range tc.unexpectedKeys {
				_, ok := getEnvVarWithExists(envVars, key)
				assert.False(t, ok, "env var %s should not be present", key)
			}
		})
	}
}

func TestPluginEnvVarsProvider_featureToggleEnvVar(t *testing.T) {
	t.Run("backend datasource with feature toggle", func(t *testing.T) {
		expectedFeatures := []string{"feat-1", "feat-2"}
		featuresLookup := map[string]bool{
			expectedFeatures[0]: true,
			expectedFeatures[1]: true,
		}

		cfg := &PluginInstanceCfg{
			Features: featuremgmt.WithFeatures(expectedFeatures[0], true, expectedFeatures[1], true),
		}

		p := NewEnvVarsProvider(cfg, nil, &fakeSSOSettingsProvider{})
		envVars := p.PluginEnvVars(context.Background(), &plugins.Plugin{})
		assert.Equal(t, 2, len(envVars))

		toggleExpression := strings.Split(envVars[1], "=")
		assert.Equal(t, 2, len(toggleExpression))

		assert.Equal(t, "GF_INSTANCE_FEATURE_TOGGLES_ENABLE", toggleExpression[0])

		toggleArgs := toggleExpression[1]
		features := strings.Split(toggleArgs, ",")

		assert.Equal(t, len(expectedFeatures), len(features))

		// this is necessary because the features are not returned in the order they are provided
		for _, f := range features {
			_, ok := featuresLookup[f]
			assert.True(t, ok)
		}
	})
}

func TestPluginEnvVarsProvider_azureEnvVars(t *testing.T) {
	t.Run("backend datasource with azure settings", func(t *testing.T) {
		cfg := &setting.Cfg{
			Raw: ini.Empty(),
			Azure: &azsettings.AzureSettings{
				AzureAuthEnabled:        true,
				Cloud:                   azsettings.AzurePublic,
				ManagedIdentityEnabled:  true,
				ManagedIdentityClientId: "mock_managed_identity_client_id",
				WorkloadIdentityEnabled: true,
				WorkloadIdentitySettings: &azsettings.WorkloadIdentitySettings{
					TenantId:  "mock_workload_identity_tenant_id",
					ClientId:  "mock_workload_identity_client_id",
					TokenFile: "mock_workload_identity_token_file",
				},
				UserIdentityEnabled:                    true,
				UserIdentityFallbackCredentialsEnabled: true,
				UserIdentityTokenEndpoint: &azsettings.TokenEndpointSettings{
					TokenUrl:          "mock_user_identity_token_url",
					ClientId:          "mock_user_identity_client_id",
					ClientSecret:      "mock_user_identity_client_secret",
					UsernameAssertion: true,
				},
			},
		}

		pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures())
		require.NoError(t, err)

		provider := NewEnvVarsProvider(pCfg, nil, &fakeSSOSettingsProvider{})
		envVars := provider.PluginEnvVars(context.Background(), &plugins.Plugin{})
		assert.ElementsMatch(t, []string{"GF_VERSION=", "GFAZPL_AZURE_CLOUD=AzureCloud", "GFAZPL_AZURE_AUTH_ENABLED=true",
			"GFAZPL_MANAGED_IDENTITY_ENABLED=true",
			"GFAZPL_MANAGED_IDENTITY_CLIENT_ID=mock_managed_identity_client_id",
			"GFAZPL_WORKLOAD_IDENTITY_ENABLED=true",
			"GFAZPL_WORKLOAD_IDENTITY_TENANT_ID=mock_workload_identity_tenant_id",
			"GFAZPL_WORKLOAD_IDENTITY_CLIENT_ID=mock_workload_identity_client_id",
			"GFAZPL_WORKLOAD_IDENTITY_TOKEN_FILE=mock_workload_identity_token_file",
			"GFAZPL_USER_IDENTITY_ENABLED=true",
			"GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED=true",
			"GFAZPL_USER_IDENTITY_TOKEN_URL=mock_user_identity_token_url",
			"GFAZPL_USER_IDENTITY_CLIENT_ID=mock_user_identity_client_id",
			"GFAZPL_USER_IDENTITY_CLIENT_SECRET=mock_user_identity_client_secret",
			"GFAZPL_USER_IDENTITY_ASSERTION=username",
		}, envVars)
	})

	t.Run("sets user token endpoint settings from SSO settings for an Azure plugin", func(t *testing.T) {
		cfg := &setting.Cfg{
			Raw: ini.Empty(),
			Azure: &azsettings.AzureSettings{
				AzureAuthEnabled:        true,
				Cloud:                   azsettings.AzurePublic,
				ManagedIdentityEnabled:  true,
				ManagedIdentityClientId: "mock_managed_identity_client_id",
				WorkloadIdentityEnabled: true,
				WorkloadIdentitySettings: &azsettings.WorkloadIdentitySettings{
					TenantId:  "mock_workload_identity_tenant_id",
					ClientId:  "mock_workload_identity_client_id",
					TokenFile: "mock_workload_identity_token_file",
				},
				UserIdentityEnabled:                    true,
				UserIdentityFallbackCredentialsEnabled: true,
				UserIdentityTokenEndpoint: &azsettings.TokenEndpointSettings{
					TokenUrl:          "mock_user_identity_token_url",
					ClientId:          "mock_user_identity_client_id",
					ClientSecret:      "mock_user_identity_client_secret",
					UsernameAssertion: true,
				},
			},
		}

		pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures())
		require.NoError(t, err)

		provider := NewEnvVarsProvider(pCfg, nil, &fakeSSOSettingsProvider{
			GetForProviderFunc: getAzureSSOSettings,
		})
		envVars := provider.PluginEnvVars(context.Background(), &plugins.Plugin{})
		assert.ElementsMatch(t, []string{"GF_VERSION=", "GFAZPL_AZURE_CLOUD=AzureCloud", "GFAZPL_AZURE_AUTH_ENABLED=true",
			"GFAZPL_MANAGED_IDENTITY_ENABLED=true",
			"GFAZPL_MANAGED_IDENTITY_CLIENT_ID=mock_managed_identity_client_id",
			"GFAZPL_WORKLOAD_IDENTITY_ENABLED=true",
			"GFAZPL_WORKLOAD_IDENTITY_TENANT_ID=mock_workload_identity_tenant_id",
			"GFAZPL_WORKLOAD_IDENTITY_CLIENT_ID=mock_workload_identity_client_id",
			"GFAZPL_WORKLOAD_IDENTITY_TOKEN_FILE=mock_workload_identity_token_file",
			"GFAZPL_USER_IDENTITY_ENABLED=true",
			"GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED=true",
			"GFAZPL_USER_IDENTITY_TOKEN_URL=sso_user_identity_token_url",
			"GFAZPL_USER_IDENTITY_CLIENT_AUTHENTICATION=sso_user_client_authentication",
			"GFAZPL_USER_IDENTITY_CLIENT_ID=sso_user_identity_client_id",
			"GFAZPL_USER_IDENTITY_CLIENT_SECRET=sso_user_identity_client_secret",
			"GFAZPL_USER_IDENTITY_MANAGED_IDENTITY_CLIENT_ID=sso_user_identity_managed_identity_client_id",
			"GFAZPL_USER_IDENTITY_FEDERATED_CREDENTIAL_AUDIENCE=sso_user_identity_federated_credential_audience",
			"GFAZPL_USER_IDENTITY_ASSERTION=username",
		}, envVars)
	})

	t.Run("does not use SSO settings if overrides have been set for an Azure plugin", func(t *testing.T) {
		cfg := &setting.Cfg{
			Raw: ini.Empty(),
			Azure: &azsettings.AzureSettings{
				AzureAuthEnabled:        true,
				Cloud:                   azsettings.AzurePublic,
				ManagedIdentityEnabled:  true,
				ManagedIdentityClientId: "mock_managed_identity_client_id",
				WorkloadIdentityEnabled: true,
				WorkloadIdentitySettings: &azsettings.WorkloadIdentitySettings{
					TenantId:  "mock_workload_identity_tenant_id",
					ClientId:  "mock_workload_identity_client_id",
					TokenFile: "mock_workload_identity_token_file",
				},
				UserIdentityEnabled:                    true,
				UserIdentityFallbackCredentialsEnabled: true,
				UserIdentityTokenEndpoint: &azsettings.TokenEndpointSettings{
					TokenUrl:                            "override_user_identity_token_url",
					TokenUrlOverride:                    true,
					ClientAuthentication:                "override_user_client_authentication",
					ClientAuthenticationOverride:        true,
					ClientId:                            "override_user_identity_client_id",
					ClientIdOverride:                    true,
					ClientSecret:                        "override_user_identity_client_secret",
					ClientSecretOverride:                true,
					ManagedIdentityClientId:             "override_user_identity_managed_identity_client_id",
					ManagedIdentityClientIdOverride:     true,
					FederatedCredentialAudience:         "override_user_identity_federated_credential_audience",
					FederatedCredentialAudienceOverride: true,
				},
			},
		}

		pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures())
		require.NoError(t, err)

		provider := NewEnvVarsProvider(pCfg, nil, &fakeSSOSettingsProvider{
			GetForProviderFunc: getAzureSSOSettings,
		})
		envVars := provider.PluginEnvVars(context.Background(), &plugins.Plugin{})
		assert.ElementsMatch(t, []string{"GF_VERSION=", "GFAZPL_AZURE_CLOUD=AzureCloud", "GFAZPL_AZURE_AUTH_ENABLED=true",
			"GFAZPL_MANAGED_IDENTITY_ENABLED=true",
			"GFAZPL_MANAGED_IDENTITY_CLIENT_ID=mock_managed_identity_client_id",
			"GFAZPL_WORKLOAD_IDENTITY_ENABLED=true",
			"GFAZPL_WORKLOAD_IDENTITY_TENANT_ID=mock_workload_identity_tenant_id",
			"GFAZPL_WORKLOAD_IDENTITY_CLIENT_ID=mock_workload_identity_client_id",
			"GFAZPL_WORKLOAD_IDENTITY_TOKEN_FILE=mock_workload_identity_token_file",
			"GFAZPL_USER_IDENTITY_ENABLED=true",
			"GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED=true",
			"GFAZPL_USER_IDENTITY_TOKEN_URL=override_user_identity_token_url",
			"GFAZPL_USER_IDENTITY_CLIENT_AUTHENTICATION=override_user_client_authentication",
			"GFAZPL_USER_IDENTITY_CLIENT_ID=override_user_identity_client_id",
			"GFAZPL_USER_IDENTITY_CLIENT_SECRET=override_user_identity_client_secret",
			"GFAZPL_USER_IDENTITY_MANAGED_IDENTITY_CLIENT_ID=override_user_identity_managed_identity_client_id",
			"GFAZPL_USER_IDENTITY_FEDERATED_CREDENTIAL_AUDIENCE=override_user_identity_federated_credential_audience",
		}, envVars)
	})
}
