package template

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math/rand"
	"regexp"
	"strings"
	"testing"

	kapi "k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/runtime"
	"k8s.io/kubernetes/pkg/util/diff"
	"k8s.io/kubernetes/pkg/util/validation/field"

	"github.com/openshift/origin/pkg/template/api"
	"github.com/openshift/origin/pkg/template/api/v1"
	"github.com/openshift/origin/pkg/template/generator"

	_ "github.com/openshift/origin/pkg/api/install"
)

func makeParameter(name, value, generate string, required bool) api.Parameter {
	return api.Parameter{
		Name:     name,
		Value:    value,
		Generate: generate,
		Required: required,
	}
}

func TestAddParameter(t *testing.T) {
	var template api.Template

	jsonData, _ := ioutil.ReadFile("../../test/templates/testdata/guestbook.json")
	json.Unmarshal(jsonData, &template)

	AddParameter(&template, makeParameter("CUSTOM_PARAM", "1", "", false))
	AddParameter(&template, makeParameter("CUSTOM_PARAM", "2", "", false))

	if p := GetParameterByName(&template, "CUSTOM_PARAM"); p == nil {
		t.Errorf("Unable to add a custom parameter to the template")
	} else {
		if p.Value != "2" {
			t.Errorf("Unable to replace the custom parameter value in template")
		}
	}
}

type FooGenerator struct {
}

func (g FooGenerator) GenerateValue(expression string) (interface{}, error) {
	return "foo", nil
}

type ErrorGenerator struct {
}

func (g ErrorGenerator) GenerateValue(expression string) (interface{}, error) {
	return "", fmt.Errorf("error")
}

type NoStringGenerator struct {
}

func (g NoStringGenerator) GenerateValue(expression string) (interface{}, error) {
	return NoStringGenerator{}, nil
}

type EmptyGenerator struct {
}

func (g EmptyGenerator) GenerateValue(expression string) (interface{}, error) {
	return "", nil
}

func TestParameterGenerators(t *testing.T) {
	tests := []struct {
		parameter  api.Parameter
		generators map[string]generator.Generator
		shouldPass bool
		expected   api.Parameter
		errType    field.ErrorType
		fieldPath  string
	}{
		{ // Empty generator, should pass
			makeParameter("PARAM-pass-empty-gen", "X", "", false),
			map[string]generator.Generator{},
			true,
			makeParameter("PARAM-pass-empty-gen", "X", "", false),
			"",
			"",
		},
		{ // Foo generator, should pass
			makeParameter("PARAM-pass-foo-gen", "", "foo", false),
			map[string]generator.Generator{"foo": FooGenerator{}},
			true,
			makeParameter("PARAM-pass-foo-gen", "foo", "", false),
			"",
			"",
		},
		{ // Foo generator, should fail
			makeParameter("PARAM-fail-foo-gen", "", "foo", false),
			map[string]generator.Generator{},
			false,
			makeParameter("PARAM-fail-foo-gen", "foo", "", false),
			field.ErrorTypeInvalid,
			"template.parameters[0]",
		},
		{ // No str generator, should fail
			makeParameter("PARAM-fail-nostr-gen", "", "foo", false),
			map[string]generator.Generator{"foo": NoStringGenerator{}},
			false,
			makeParameter("PARAM-fail-nostr-gen", "foo", "", false),
			field.ErrorTypeInvalid,
			"template.parameters[0]",
		},
		{ // Invalid generator, should fail
			makeParameter("PARAM-fail-inv-gen", "", "invalid", false),
			map[string]generator.Generator{"invalid": nil},
			false,
			makeParameter("PARAM-fail-inv-gen", "", "invalid", false),
			field.ErrorTypeInvalid,
			"template.parameters[0]",
		},
		{ // Error generator, should fail
			makeParameter("PARAM-fail-err-gen", "", "error", false),
			map[string]generator.Generator{"error": ErrorGenerator{}},
			false,
			makeParameter("PARAM-fail-err-gen", "", "error", false),
			field.ErrorTypeInvalid,
			"template.parameters[0]",
		},
		{ // Error required parameter, no value, should fail
			makeParameter("PARAM-fail-no-val", "", "", true),
			map[string]generator.Generator{"error": ErrorGenerator{}},
			false,
			makeParameter("PARAM-fail-no-val", "", "", true),
			field.ErrorTypeRequired,
			"template.parameters[0]",
		},
		{ // Error required parameter, no value from generator, should fail
			makeParameter("PARAM-fail-no-val-from-gen", "", "empty", true),
			map[string]generator.Generator{"empty": EmptyGenerator{}},
			false,
			makeParameter("PARAM-fail-no-val-from-gen", "", "empty", true),
			field.ErrorTypeRequired,
			"template.parameters[0]",
		},
	}

	for i, test := range tests {
		processor := NewProcessor(test.generators)
		template := api.Template{Parameters: []api.Parameter{test.parameter}}
		err := processor.GenerateParameterValues(&template)
		if err != nil && test.shouldPass {
			t.Errorf("test[%v]: Unexpected error %v", i, err)
		}
		if err == nil && !test.shouldPass {
			t.Errorf("test[%v]: Expected error", i)
		}
		if err != nil {
			if test.errType != err.Type {
				t.Errorf("test[%v]: Unexpected error type: Expected: %s, got %s", i, test.errType, err.Type)
			}
			if test.fieldPath != err.Field {
				t.Errorf("test[%v]: Unexpected error type: Expected: %s, got %s", i, test.fieldPath, err.Field)
			}
			continue
		}
		actual := template.Parameters[0]
		if actual.Value != test.expected.Value {
			t.Errorf("test[%v]: Unexpected value: Expected: %#v, got: %#v", i, test.expected.Value, test.parameter.Value)
		}
	}
}

func TestProcessValue(t *testing.T) {
	var template api.Template
	if err := runtime.DecodeInto(kapi.Codecs.UniversalDecoder(), []byte(`{
		"kind":"Template", "apiVersion":"v1",
		"objects": [
			{
				"kind": "Service", "apiVersion": "v${VALUE}",
				"metadata": {
					"labels": {
						"i1": "${{INT_1}}",
						"invalidjsonmap": "${{INVALID_JSON_MAP}}",
						"invalidjsonarray": "${{INVALID_JSON_ARRAY}}",
						"key1": "${VALUE}",
						"key2": "$${VALUE}",
						"quoted_string": "${{STRING_1}}",
						"s1_s1": "${STRING_1}_${STRING_1}",
						"s1_s2": "${STRING_1}_${STRING_2}",
						"untouched": "a${{INT_1}}",
						"untouched2": "${{INT_1}}a",
						"untouched3": "${{INVALID_PARAMETER}}",
						"untouched4": "${{INVALID PARAMETER}}",
						"validjsonmap": "${{VALID_JSON_MAP}}",
						"validjsonarray": "${{VALID_JSON_ARRAY}}"

					}
				}
			}
		]
	}`), &template); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	generators := map[string]generator.Generator{
		"expression": generator.NewExpressionValueGenerator(rand.New(rand.NewSource(1337))),
	}
	processor := NewProcessor(generators)

	// Define custom parameter for the transformation:
	AddParameter(&template, makeParameter("VALUE", "1", "", false))
	AddParameter(&template, makeParameter("STRING_1", "string1", "", false))
	AddParameter(&template, makeParameter("STRING_2", "string2", "", false))
	AddParameter(&template, makeParameter("INT_1", "1", "", false))
	AddParameter(&template, makeParameter("VALID_JSON_MAP", "{\"key\":\"value\"}", "", false))
	AddParameter(&template, makeParameter("INVALID_JSON_MAP", "{\"key\":\"value\"", "", false))
	AddParameter(&template, makeParameter("VALID_JSON_ARRAY", "[\"key\",\"value\"]", "", false))
	AddParameter(&template, makeParameter("INVALID_JSON_ARRAY", "[\"key\":\"value\"", "", false))

	// Transform the template config into the result config
	errs := processor.Process(&template)
	if len(errs) > 0 {
		t.Fatalf("unexpected error: %v", errs)
	}
	result, err := runtime.Encode(kapi.Codecs.LegacyCodec(v1.SchemeGroupVersion), &template)
	if err != nil {
		t.Fatalf("unexpected error during encoding Config: %#v", err)
	}
	expect := `{"kind":"Template","apiVersion":"v1","metadata":{"creationTimestamp":null},"objects":[{"apiVersion":"v1","kind":"Service","metadata":{"labels":{"i1":1,"invalidjsonarray":"[\"key\":\"value\"","invalidjsonmap":"{\"key\":\"value\"","key1":"1","key2":"$1","quoted_string":"string1","s1_s1":"string1_string1","s1_s2":"string1_string2","untouched":"a${{INT_1}}","untouched2":"${{INT_1}}a","untouched3":"${{INVALID_PARAMETER}}","untouched4":"${{INVALID PARAMETER}}","validjsonarray":["key","value"],"validjsonmap":{"key":"value"}}}}],"parameters":[{"name":"VALUE","value":"1"},{"name":"STRING_1","value":"string1"},{"name":"STRING_2","value":"string2"},{"name":"INT_1","value":"1"},{"name":"VALID_JSON_MAP","value":"{\"key\":\"value\"}"},{"name":"INVALID_JSON_MAP","value":"{\"key\":\"value\""},{"name":"VALID_JSON_ARRAY","value":"[\"key\",\"value\"]"},{"name":"INVALID_JSON_ARRAY","value":"[\"key\":\"value\""}]}`
	stringResult := strings.TrimSpace(string(result))
	if expect != stringResult {
		//t.Errorf("unexpected output, expected: \n%s\nGot:\n%s\n", expect, stringResult)
		t.Errorf("unexpected output: %s", diff.StringDiff(expect, stringResult))
	}
}

var trailingWhitespace = regexp.MustCompile(`\n\s*`)

func TestEvaluateLabels(t *testing.T) {
	testCases := map[string]struct {
		Input  string
		Output string
		Labels map[string]string
	}{
		"no labels": {
			Input: `{
				"kind":"Template", "apiVersion":"v1",
				"objects": [
					{
						"kind": "Service", "apiVersion": "v1",
						"metadata": {"labels": {"key1": "v1", "key2": "v2"}	}
					}
				]
			}`,
			Output: `{
				"kind":"Template","apiVersion":"v1","metadata":{"creationTimestamp":null},
				"objects":[
					{
						"apiVersion":"v1","kind":"Service","metadata":{
						"labels":{"key1":"v1","key2":"v2"}}
					}
				]
			}`,
		},
		"one different label": {
			Input: `{
				"kind":"Template", "apiVersion":"v1",
				"objects": [
					{
						"kind": "Service", "apiVersion": "v1",
						"metadata": {"labels": {"key1": "v1", "key2": "v2"}	}
					}
				]
			}`,
			Output: `{
				"kind":"Template","apiVersion":"v1","metadata":{"creationTimestamp":null},
				"objects":[
					{
						"apiVersion":"v1","kind":"Service","metadata":{
						"labels":{"key1":"v1","key2":"v2","key3":"v3"}}
					}
				],
				"labels":{"key3":"v3"}
			}`,
			Labels: map[string]string{"key3": "v3"},
		},
		"when the root object has labels and metadata": {
			Input: `{
				"kind":"Template", "apiVersion":"v1",
				"objects": [
					{
						"kind": "Service", "apiVersion": "v1",
						"metadata": {},
						"labels": {
							"key1": "v1",
							"key2": "v2"
						}
					}
				]
			}`,
			Output: `{
				"kind":"Template","apiVersion":"v1","metadata":{"creationTimestamp":null},
				"objects":[
					{
						"apiVersion":"v1","kind":"Service",
						"labels":{"key1":"v1","key2":"v2"},
						"metadata":{"labels":{"key3":"v3"}}
					}
				],
				"labels":{"key3":"v3"}
			}`,
			Labels: map[string]string{"key3": "v3"},
		},
		"overwrites label": {
			Input: `{
				"kind":"Template", "apiVersion":"v1",
				"objects": [
					{
						"kind": "Service", "apiVersion": "v1",
						"metadata": {"labels": {"key1": "v1", "key2": "v2"}	}
					}
				]
			}`,
			Output: `{
				"kind":"Template","apiVersion":"v1","metadata":{"creationTimestamp":null},
				"objects":[
					{
						"apiVersion":"v1","kind":"Service","metadata":{
						"labels":{"key1":"v1","key2":"v3"}}
					}
				],
				"labels":{"key2":"v3"}
			}`,
			Labels: map[string]string{"key2": "v3"},
		},
	}

	for k, testCase := range testCases {
		var template api.Template
		if err := runtime.DecodeInto(kapi.Codecs.UniversalDecoder(), []byte(testCase.Input), &template); err != nil {
			t.Errorf("%s: unexpected error: %v", k, err)
			continue
		}

		generators := map[string]generator.Generator{
			"expression": generator.NewExpressionValueGenerator(rand.New(rand.NewSource(1337))),
		}
		processor := NewProcessor(generators)

		template.ObjectLabels = testCase.Labels

		// Transform the template config into the result config
		errs := processor.Process(&template)
		if len(errs) > 0 {
			t.Errorf("%s: unexpected error: %v", k, errs)
			continue
		}
		result, err := runtime.Encode(kapi.Codecs.LegacyCodec(v1.SchemeGroupVersion), &template)
		if err != nil {
			t.Errorf("%s: unexpected error: %v", k, err)
			continue
		}
		expect := testCase.Output
		expect = trailingWhitespace.ReplaceAllString(expect, "")
		stringResult := strings.TrimSpace(string(result))
		if expect != stringResult {
			t.Errorf("%s: unexpected output: %s", k, diff.StringDiff(expect, stringResult))
			continue
		}
	}
}

func TestProcessTemplateParameters(t *testing.T) {
	var template, expectedTemplate api.Template
	jsonData, _ := ioutil.ReadFile("../../test/templates/testdata/guestbook.json")
	if err := runtime.DecodeInto(kapi.Codecs.UniversalDecoder(), jsonData, &template); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	expectedData, _ := ioutil.ReadFile("../../test/templates/testdata/guestbook_list.json")
	if err := runtime.DecodeInto(kapi.Codecs.UniversalDecoder(), expectedData, &expectedTemplate); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	generators := map[string]generator.Generator{
		"expression": generator.NewExpressionValueGenerator(rand.New(rand.NewSource(1337))),
	}
	processor := NewProcessor(generators)

	// Define custom parameter for the transformation:
	AddParameter(&template, makeParameter("CUSTOM_PARAM1", "1", "", false))

	// Transform the template config into the result config
	errs := processor.Process(&template)
	if len(errs) > 0 {
		t.Fatalf("unexpected error: %v", errs)
	}
	result, err := runtime.Encode(kapi.Codecs.LegacyCodec(v1.SchemeGroupVersion), &template)
	if err != nil {
		t.Fatalf("unexpected error during encoding Config: %#v", err)
	}
	exp, _ := runtime.Encode(kapi.Codecs.LegacyCodec(v1.SchemeGroupVersion), &expectedTemplate)

	if string(result) != string(exp) {
		t.Errorf("unexpected output: %s", diff.StringDiff(string(exp), string(result)))
	}
}