package pluginproxy

import (
	"context"
	"testing"

	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/plugins"
	"github.com/grafana/grafana/pkg/setting"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

var getAccessTokenFunc func(credential TokenCredential, scopes []string)

type tokenCacheFake struct{}

func (c *tokenCacheFake) GetAccessToken(_ context.Context, credential TokenCredential, scopes []string) (string, error) {
	getAccessTokenFunc(credential, scopes)
	return "4cb83b87-0ffb-4abd-82f6-48a8c08afc53", nil
}

func TestAzureTokenProvider_isManagedIdentityCredential(t *testing.T) {
	ctx := context.Background()

	cfg := &setting.Cfg{}

	ds := &models.DataSource{Id: 1, Version: 2}
	route := &plugins.AppPluginRoute{}

	authParams := &plugins.JwtTokenAuth{
		Scopes: []string{
			"https://management.azure.com/.default",
		},
		Params: map[string]string{
			"azure_auth_type": "",
			"azure_cloud":     "AzureCloud",
			"tenant_id":       "",
			"client_id":       "",
			"client_secret":   "",
		},
	}

	provider := newAzureAccessTokenProvider(ctx, cfg, ds, route, authParams)

	t.Run("when managed identities enabled", func(t *testing.T) {
		cfg.Azure.ManagedIdentityEnabled = true

		t.Run("should be managed identity if auth type is managed identity", func(t *testing.T) {
			authParams.Params = map[string]string{
				"azure_auth_type": "msi",
			}

			assert.True(t, provider.isManagedIdentityCredential())
		})

		t.Run("should be client secret if auth type is client secret", func(t *testing.T) {
			authParams.Params = map[string]string{
				"azure_auth_type": "clientsecret",
			}

			assert.False(t, provider.isManagedIdentityCredential())
		})

		t.Run("should be managed identity if datasource not configured", func(t *testing.T) {
			authParams.Params = map[string]string{
				"azure_auth_type": "",
				"tenant_id":       "",
				"client_id":       "",
				"client_secret":   "",
			}

			assert.True(t, provider.isManagedIdentityCredential())
		})

		t.Run("should be client secret if auth type not specified but credentials configured", func(t *testing.T) {
			authParams.Params = map[string]string{
				"azure_auth_type": "",
				"tenant_id":       "06da9207-bdd9-4558-aee4-377450893cb4",
				"client_id":       "b8c58fe8-1fca-4e30-a0a8-b44d0e5f70d6",
				"client_secret":   "9bcd4434-824f-4887-a8a8-94c287bf0a7b",
			}

			assert.False(t, provider.isManagedIdentityCredential())
		})
	})

	t.Run("when managed identities disabled", func(t *testing.T) {
		cfg.Azure.ManagedIdentityEnabled = false

		t.Run("should be managed identity if auth type is managed identity", func(t *testing.T) {
			authParams.Params = map[string]string{
				"azure_auth_type": "msi",
			}

			assert.True(t, provider.isManagedIdentityCredential())
		})

		t.Run("should be client secret if datasource not configured", func(t *testing.T) {
			authParams.Params = map[string]string{
				"azure_auth_type": "",
				"tenant_id":       "",
				"client_id":       "",
				"client_secret":   "",
			}

			assert.False(t, provider.isManagedIdentityCredential())
		})
	})
}

func TestAzureTokenProvider_getAccessToken(t *testing.T) {
	ctx := context.Background()

	cfg := &setting.Cfg{}

	ds := &models.DataSource{Id: 1, Version: 2}
	route := &plugins.AppPluginRoute{}

	authParams := &plugins.JwtTokenAuth{
		Scopes: []string{
			"https://management.azure.com/.default",
		},
		Params: map[string]string{
			"azure_auth_type": "",
			"azure_cloud":     "AzureCloud",
			"tenant_id":       "",
			"client_id":       "",
			"client_secret":   "",
		},
	}

	provider := newAzureAccessTokenProvider(ctx, cfg, ds, route, authParams)

	original := azureTokenCache
	azureTokenCache = &tokenCacheFake{}
	t.Cleanup(func() { azureTokenCache = original })

	t.Run("when managed identities enabled", func(t *testing.T) {
		cfg.Azure.ManagedIdentityEnabled = true

		t.Run("should resolve managed identity credential if auth type is managed identity", func(t *testing.T) {
			authParams.Params = map[string]string{
				"azure_auth_type": "msi",
			}

			getAccessTokenFunc = func(credential TokenCredential, scopes []string) {
				assert.IsType(t, &managedIdentityCredential{}, credential)
			}

			_, err := provider.getAccessToken()
			require.NoError(t, err)
		})

		t.Run("should resolve client secret credential if auth type is client secret", func(t *testing.T) {
			authParams.Params = map[string]string{
				"azure_auth_type": "clientsecret",
			}

			getAccessTokenFunc = func(credential TokenCredential, scopes []string) {
				assert.IsType(t, &clientSecretCredential{}, credential)
			}

			_, err := provider.getAccessToken()
			require.NoError(t, err)
		})
	})

	t.Run("when managed identities disabled", func(t *testing.T) {
		cfg.Azure.ManagedIdentityEnabled = false

		t.Run("should return error if auth type is managed identity", func(t *testing.T) {
			authParams.Params = map[string]string{
				"azure_auth_type": "msi",
			}

			getAccessTokenFunc = func(credential TokenCredential, scopes []string) {
				assert.Fail(t, "token cache not expected to be called")
			}

			_, err := provider.getAccessToken()
			require.Error(t, err)
		})
	})
}

func TestAzureTokenProvider_getClientSecretCredential(t *testing.T) {
	ctx := context.Background()

	cfg := &setting.Cfg{}

	ds := &models.DataSource{Id: 1, Version: 2}
	route := &plugins.AppPluginRoute{}

	authParams := &plugins.JwtTokenAuth{
		Scopes: []string{
			"https://management.azure.com/.default",
		},
		Params: map[string]string{
			"azure_auth_type": "",
			"azure_cloud":     "AzureCloud",
			"tenant_id":       "7dcf1d1a-4ec0-41f2-ac29-c1538a698bc4",
			"client_id":       "1af7c188-e5b6-4f96-81b8-911761bdd459",
			"client_secret":   "0416d95e-8af8-472c-aaa3-15c93c46080a",
		},
	}

	provider := newAzureAccessTokenProvider(ctx, cfg, ds, route, authParams)

	t.Run("should return clientSecretCredential with values", func(t *testing.T) {
		result := provider.getClientSecretCredential()
		assert.IsType(t, &clientSecretCredential{}, result)

		credential := (result).(*clientSecretCredential)

		assert.Equal(t, "https://login.microsoftonline.com/", credential.authority)
		assert.Equal(t, "7dcf1d1a-4ec0-41f2-ac29-c1538a698bc4", credential.tenantId)
		assert.Equal(t, "1af7c188-e5b6-4f96-81b8-911761bdd459", credential.clientId)
		assert.Equal(t, "0416d95e-8af8-472c-aaa3-15c93c46080a", credential.clientSecret)
	})
}
