/**
 * Copyright 2018 Curtis Mattoon
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package secret

import (
	//"reflect"
	"context"
	"testing"

	"github.com/cmattoon/aws-ssm/pkg/provider"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestParseStringList(t *testing.T) {
	for _, tc := range []struct {
		title    string
		pvalue   string
		expected map[string]string
	}{
		{
			title:    "parse empty value",
			pvalue:   "",
			expected: map[string]string{},
		},
		{
			title:    "parse whitespace-only value",
			pvalue:   "    ",
			expected: map[string]string{},
		},
		{
			title:  "parse some simple values",
			pvalue: "key1=val1,key2=val2,key3=val3,key4=val4",
			expected: map[string]string{
				"key1": "val1",
				"key2": "val2",
				"key3": "val3",
				"key4": "val4",
			},
		},
		{
			title:  "parse equal sign in value",
			pvalue: "key1=val1,key2=val2,key3=val3,key4=val4=true",
			expected: map[string]string{
				"key1": "val1",
				"key2": "val2",
				"key3": "val3",
				"key4": "val4=true",
			},
		},
		{
			title:  "parse non-list",
			pvalue: "key1",
			expected: map[string]string{
				"key1": "",
			},
		},
		{
			title:  "parse trailing comma",
			pvalue: "key1=value1,",
			expected: map[string]string{
				"key1": "value1",
			},
		},
		{
			title:  "parse leading/trailing comma",
			pvalue: ",key1=value1,key2=val2,key3=val3,",
			expected: map[string]string{
				"key1": "value1",
				"key2": "val2",
				"key3": "val3",
			},
		},
		{
			title:  "not a StringList",
			pvalue: "asdfjkl",
			expected: map[string]string{
				"asdfjkl": "",
			},
		},
		{
			title:  "fine i guess",
			pvalue: "ThIsMiGhTBeBaSe64==",
			expected: map[string]string{
				"ThIsMiGhTBeBaSe64": "=",
			},
		},
		{
			title:  "another StringList",
			pvalue: "123,456,789",
			expected: map[string]string{
				"123": "",
				"456": "",
				"789": "",
			},
		},
	} {
		t.Run(tc.title, func(t *testing.T) {
			s := &Secret{
				Name:       "test_secret",
				Namespace:  "test",
				ParamName:  "FOO_PARAM",
				ParamType:  "StringList",
				ParamKey:   "some-string-list",
				ParamValue: tc.pvalue,
				Data:       map[string]string{},
			}
			data := s.ParseStringList()
			assert.Equal(t, tc.expected, data)
		})
	}

}

// Should set the key/value pair
func TestSet(t *testing.T) {
	s := &Secret{
		Name:       "test_secret",
		Namespace:  "test",
		ParamName:  "FOO_PARAM",
		ParamType:  "StringList",
		ParamKey:   "foo-param",
		ParamValue: "key1=val1,key2=val2,key3=val3,key4=val4=true",
		Data:       map[string]string{},
	}
	require.NoError(t, s.Set("foo", "bar"))
	assert.Equal(t, s.Secret.StringData["foo"], "bar")
}

func TestSetRefusesToOverwriteKey(t *testing.T) {
	s := &Secret{
		Name:       "test_secret",
		Namespace:  "test",
		ParamName:  "FOO_PARAM",
		ParamType:  "StringList",
		ParamKey:   "foo-param",
		ParamValue: "key1=val1,key2=val2,key3=val3,key4=val4=true",
		Data: map[string]string{
			"foo": "bar",
		},
	}
	require.Error(t, s.Set("foo", "baz"))

	assert.Equal(t, "bar", s.Data["foo"])
	// "foo=bar" was specified initially, not via Set
	// so strictly speaking, it shouldn't be set here yet
	// (because s.Set() should fail)
	assert.Equal(t, "", s.Secret.StringData["foo"])
}

func TestSetsValue(t *testing.T) {
	p := provider.MockProvider{"FooBar123", "PlaintextIsAnError", make(map[string]string)}
	s := v1.Secret{}
	ctx := context.TODO()
	testSecret, err := NewSecret(ctx, s, p, "foo-secret", "namespace", "foo-param", "String", "", "")

	assert.Equal(t, err, nil)
	assert.Equal(t, testSecret.ParamValue, "FooBar123")
}

// When the encryption key is defined, the decrypted value should be returned
func TestNewSecretDecryptsIfKeyIsSet(t *testing.T) {
	p := provider.MockProvider{"$@#*$(@)*$", "FooBar123", make(map[string]string)}
	s := v1.Secret{}
	ctx := context.TODO()
	testSecret, err := NewSecret(ctx, s, p, "foo-secret", "namespace", "foo-param", "String", "my/test/key", "")
	assert.Equal(t, nil, err)
	assert.Equal(t, testSecret.ParamValue, p.DecryptedValue)
}

func TestNewSecretHandlesStringList(t *testing.T) {
	p := provider.MockProvider{"$@#*$(@)*$", "key1=val1,key2=val2,key3=val3", make(map[string]string)}
	s := v1.Secret{}
	ctx := context.TODO()
	ts, err := NewSecret(ctx, s, p, "foo-secret", "namespace", "foo-param", "StringList", "my/test/key", "")
	assert.True(t, err == nil)
	exp := map[string]string{
		"key1": "val1",
		"key2": "val2",
		"key3": "val3",
	}

	for k, v := range exp {
		v3, ok := ts.Secret.StringData[k]
		assert.Equal(t, ok, true)
		assert.Equal(t, v, v3)
	}

}

func TestFromKubernetesSecretReturnsErrorIfIrrelevant(t *testing.T) {
	p := provider.MockProvider{"$@#*$(@)*$", "FooBar123", make(map[string]string)}
	s := v1.Secret{} // No annotations, so no params
	ctx := context.TODO()

	_, err := FromKubernetesSecret(ctx, p, s)
	if err.Error() != "Irrelevant Secret" {
		t.Fail()
	}
}

// If the parameter is of Type=SecureString, and no key is supplied,
// attempt to use the default key.
func TestFromKubernetesSecretUsesDefaultEncryptionKey(t *testing.T) {
	p := provider.MockProvider{"$@#*$(@)*$", "FooBar123", make(map[string]string)}

	s := v1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Annotations: map[string]string{
				"alpha.ssm.cmattoon.com/aws-param-name": "foo-param",
				"alpha.ssm.cmattoon.com/aws-param-type": "SecureString",
			},
		},
	}

	ctx := context.TODO()

	ks, err := FromKubernetesSecret(ctx, p, s)

	if err != nil || ks.ParamKey != "alias/aws/ssm" || ks.ParamValue != "FooBar123" {
		t.Fail()
	}
}

func TestFromKubernetesSecretUsesSpecifiedEncryptionKey(t *testing.T) {
	p := provider.MockProvider{"$@#*$(@)*$", "FooBar123", make(map[string]string)}

	s := v1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Annotations: map[string]string{
				"alpha.ssm.cmattoon.com/aws-param-name": "foo-param",
				"alpha.ssm.cmattoon.com/aws-param-type": "SecureString",
				"alpha.ssm.cmattoon.com/aws-param-key":  "foo/bar/baz",
			},
		},
	}

	ctx := context.TODO()

	ks, err := FromKubernetesSecret(ctx, p, s)

	if err != nil || ks.ParamKey != "foo/bar/baz" || ks.ParamValue != "FooBar123" {
		t.Fail()
	}
}

func TestSafeKeyName(t *testing.T) {
	keys := map[string]string{
		"/foo/bar":     "foo_bar",
		"/foo/bar/":    "foo_bar",
		"//foo/bar":    "_foo_bar",
		"//foo/bar/":   "_foo_bar",
		"/foo/bar/baz": "foo_bar_baz",
	}
	for path, exp := range keys {
		assert.Equal(t, safeKeyName(path), exp)
	}
}
