package promclient_test

import (
	"context"
	"errors"
	"sort"
	"strings"
	"testing"

	"github.com/grafana/grafana/pkg/tsdb/prometheus/buffered/promclient"

	apiv1 "github.com/prometheus/client_golang/api/prometheus/v1"

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

func TestCache_GetClient(t *testing.T) {
	t.Run("it caches the client for a set of auth headers", func(t *testing.T) {
		tc := setupCacheContext()

		c, err := tc.providerCache.GetClient(headers)
		require.Nil(t, err)

		c2, err := tc.providerCache.GetClient(headers)
		require.Nil(t, err)

		require.Equal(t, c, c2)
		require.Equal(t, 1, tc.clientProvider.numCalls)
	})

	t.Run("it returns different clients when the headers differ", func(t *testing.T) {
		tc := setupCacheContext()
		h1 := map[string]string{"Authorization": "token", "X-ID-Token": "id-token"}
		h2 := map[string]string{"Authorization": "token2", "X-ID-Token": "id-token"}

		c, err := tc.providerCache.GetClient(h1)
		require.Nil(t, err)

		c2, err := tc.providerCache.GetClient(h2)
		require.Nil(t, err)

		require.NotEqual(t, c, c2)
		require.Equal(t, 2, tc.clientProvider.numCalls)
	})

	t.Run("it returns from the cache when headers are the same", func(t *testing.T) {
		tc := setupCacheContext()
		h1 := map[string]string{"Authorization": "token", "X-ID-Token": "id-token"}
		h2 := map[string]string{"Authorization": "token", "X-ID-Token": "id-token"}

		c, err := tc.providerCache.GetClient(h1)
		require.Nil(t, err)

		c2, err := tc.providerCache.GetClient(h2)
		require.Nil(t, err)

		require.Equal(t, c, c2)
		require.Equal(t, 1, tc.clientProvider.numCalls)
	})

	t.Run("it doesn't cache anything when an error occurs", func(t *testing.T) {
		tc := setupCacheContext()
		tc.clientProvider.errors <- errors.New("something bad")

		_, err := tc.providerCache.GetClient(headers)
		require.EqualError(t, err, "something bad")

		c, err := tc.providerCache.GetClient(headers)
		require.Nil(t, err)

		require.NotNil(t, c)
		require.Equal(t, 2, tc.clientProvider.numCalls)
	})
}

type cacheTestContext struct {
	providerCache  *promclient.ProviderCache
	clientProvider *fakePromClientProvider
}

func setupCacheContext() *cacheTestContext {
	fp := newFakePromClientProvider()
	p, err := promclient.NewProviderCache(fp)
	if err != nil {
		panic(err)
	}

	return &cacheTestContext{
		providerCache:  p,
		clientProvider: fp,
	}
}

func newFakePromClientProvider() *fakePromClientProvider {
	return &fakePromClientProvider{
		errors: make(chan error, 1),
	}
}

type fakePromClientProvider struct {
	headers  map[string]string
	numCalls int
	errors   chan error
}

func (p *fakePromClientProvider) GetClient(h map[string]string) (apiv1.API, error) {
	p.headers = h
	p.numCalls++

	var err error
	select {
	case err = <-p.errors:
	default:
	}

	var config []string
	for _, v := range h {
		config = append(config, v)
	}
	sort.Strings(config) //because map
	return &fakePromClient{config: strings.Join(config, "")}, err
}

type fakePromClient struct {
	apiv1.API
	config string
}

func (c *fakePromClient) Config(ctx context.Context) (apiv1.ConfigResult, error) {
	return apiv1.ConfigResult{YAML: c.config}, nil
}
