package kvstore

import (
	"context"
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
	"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/secrets/fakes"
	"github.com/grafana/grafana/pkg/services/secrets/manager"
)

type TestCase struct {
	OrgId     int64
	Namespace string
	Type      string
	Revision  int64
}

func (t *TestCase) Value() string {
	return fmt.Sprintf("%d:%s:%s:%d", t.OrgId, t.Namespace, t.Type, t.Revision)
}

func TestSecretsKVStoreSQL(t *testing.T) {
	sqlStore := db.InitTestDB(t)
	secretsService := manager.SetupTestService(t, fakes.NewFakeSecretsStore())
	kv := NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))

	ctx := context.Background()

	testCases := []*TestCase{
		{
			OrgId:     0,
			Namespace: "namespace1",
			Type:      "testing1",
		},
		{
			OrgId:     0,
			Namespace: "namespace2",
			Type:      "testing2",
		},
		{
			OrgId:     1,
			Namespace: "namespace1",
			Type:      "testing1",
		},
		{
			OrgId:     1,
			Namespace: "namespace3",
			Type:      "testing3",
		},
	}

	for _, tc := range testCases {
		err := kv.Set(ctx, tc.OrgId, tc.Namespace, tc.Type, tc.Value())
		require.NoError(t, err)
	}

	t.Run("get existing keys", func(t *testing.T) {
		for _, tc := range testCases {
			value, ok, err := kv.Get(ctx, tc.OrgId, tc.Namespace, tc.Type)
			require.NoError(t, err)
			require.True(t, ok)
			require.Equal(t, tc.Value(), value)
		}
	})

	t.Run("get nonexistent keys", func(t *testing.T) {
		tcs := []*TestCase{
			{
				OrgId:     0,
				Namespace: "namespace3",
				Type:      "testing3",
			},
			{
				OrgId:     1,
				Namespace: "namespace2",
				Type:      "testing2",
			},
			{
				OrgId:     2,
				Namespace: "namespace1",
				Type:      "testing1",
			},
		}

		for _, tc := range tcs {
			value, ok, err := kv.Get(ctx, tc.OrgId, tc.Namespace, tc.Type)
			require.Nil(t, err)
			require.False(t, ok)
			require.Equal(t, "", value)
		}
	})

	t.Run("modify existing key", func(t *testing.T) {
		tc := testCases[0]

		value, ok, err := kv.Get(ctx, tc.OrgId, tc.Namespace, tc.Type)
		require.NoError(t, err)
		require.True(t, ok)
		assert.Equal(t, tc.Value(), value)

		tc.Revision += 1

		err = kv.Set(ctx, tc.OrgId, tc.Namespace, tc.Type, tc.Value())
		require.NoError(t, err)

		value, ok, err = kv.Get(ctx, tc.OrgId, tc.Namespace, tc.Type)
		require.NoError(t, err)
		require.True(t, ok)
		assert.Equal(t, tc.Value(), value)
	})

	t.Run("use fixed client", func(t *testing.T) {
		tc := testCases[0]

		client := With(kv, tc.OrgId, tc.Namespace, tc.Type)
		fmt.Println(client.Namespace, client.OrgId, client.Type)

		value, ok, err := client.Get(ctx)
		require.NoError(t, err)
		require.True(t, ok)
		require.Equal(t, tc.Value(), value)

		tc.Revision += 1

		err = client.Set(ctx, tc.Value())
		require.NoError(t, err)

		value, ok, err = client.Get(ctx)
		require.NoError(t, err)
		require.True(t, ok)
		assert.Equal(t, tc.Value(), value)
	})

	t.Run("deleting keys", func(t *testing.T) {
		var stillHasKeys bool
		for _, tc := range testCases {
			if _, ok, err := kv.Get(ctx, tc.OrgId, tc.Namespace, tc.Type); err == nil && ok {
				stillHasKeys = true
				break
			}
		}
		require.True(t, stillHasKeys,
			"we are going to test key deletion, but there are no keys to delete in the database")
		for _, tc := range testCases {
			err := kv.Del(ctx, tc.OrgId, tc.Namespace, tc.Type)
			require.NoError(t, err)
		}
		for _, tc := range testCases {
			_, ok, err := kv.Get(ctx, tc.OrgId, tc.Namespace, tc.Type)
			require.NoError(t, err)
			require.False(t, ok, "all keys should be deleted at this point")
		}
	})

	t.Run("listing existing keys", func(t *testing.T) {
		sqlStore := db.InitTestDB(t)
		secretsService := manager.SetupTestService(t, fakes.NewFakeSecretsStore())
		kv := NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))

		ctx := context.Background()

		namespace, typ := "listtest", "listtest"

		testCases := []*TestCase{
			{
				OrgId:     1,
				Type:      typ,
				Namespace: namespace,
			},
			{
				OrgId:     2,
				Type:      typ,
				Namespace: namespace,
			},
			{
				OrgId:     3,
				Type:      typ,
				Namespace: namespace,
			},
			{
				OrgId:     4,
				Type:      typ,
				Namespace: namespace,
			},
			{
				OrgId:     1,
				Type:      typ,
				Namespace: "other_key",
			},
			{
				OrgId:     4,
				Type:      typ,
				Namespace: "another_one",
			},
		}

		for _, tc := range testCases {
			err := kv.Set(ctx, tc.OrgId, tc.Namespace, tc.Type, tc.Value())
			require.NoError(t, err)
		}

		keys, err := kv.Keys(ctx, AllOrganizations, namespace, typ)

		require.NoError(t, err)
		require.Len(t, keys, 4)

		found := 0

		for _, key := range keys {
			for _, tc := range testCases {
				if key.OrgId == tc.OrgId && key.Namespace == tc.Namespace && key.Type == tc.Type {
					found++
					break
				}
			}
		}

		require.Equal(t, 4, found, "querying for all orgs should return 4 records")

		keys, err = kv.Keys(ctx, 1, namespace, typ)

		require.NoError(t, err)
		require.Len(t, keys, 1, "querying for a specific org should return 1 record")

		keys, err = kv.Keys(ctx, AllOrganizations, "not_existing_namespace", "not_existing_type")
		require.NoError(t, err, "querying a not existing namespace should not throw an error")
		require.Len(t, keys, 0, "querying a not existing namespace should return an empty slice")
	})

	t.Run("getting all secrets", func(t *testing.T) {
		sqlStore := db.InitTestDB(t)
		secretsService := manager.SetupTestService(t, fakes.NewFakeSecretsStore())
		kv := NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))

		ctx := context.Background()

		namespace, typ := "listtest", "listtest"

		testCases := []*TestCase{
			{
				OrgId:     1,
				Type:      typ,
				Namespace: namespace,
			},
			{
				OrgId:     2,
				Type:      typ,
				Namespace: namespace,
			},
			{
				OrgId:     3,
				Type:      typ,
				Namespace: namespace,
			},
			{
				OrgId:     4,
				Type:      typ,
				Namespace: namespace,
			},
			{
				OrgId:     1,
				Type:      typ,
				Namespace: "other_key",
			},
			{
				OrgId:     4,
				Type:      typ,
				Namespace: "another_one",
			},
		}

		for _, tc := range testCases {
			err := kv.Set(ctx, tc.OrgId, tc.Namespace, tc.Type, tc.Value())
			require.NoError(t, err)
		}

		secrets, err := kv.GetAll(ctx)

		require.NoError(t, err)
		require.Len(t, secrets, 6)

		found := 0

		for _, s := range secrets {
			for _, tc := range testCases {
				if *s.OrgId == tc.OrgId &&
					*s.Namespace == tc.Namespace &&
					*s.Type == tc.Type {
					require.Equal(t, tc.Value(), s.Value, "secret found but value is not equals")
					found++
					break
				}
			}
		}

		require.Equal(t, 6, found, "querying for all secrets should return 6 records")
	})
}
