package packer

import (
	"cgl.tideland.biz/asserts"
	"io/ioutil"
	"os"
	"reflect"
	"sort"
	"testing"
)

func TestParseTemplateFile_basic(t *testing.T) {
	data := `
	{
		"builders": [{"type": "something"}]
	}
	`

	tf, err := ioutil.TempFile("", "packer")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	tf.Write([]byte(data))
	tf.Close()

	result, err := ParseTemplateFile(tf.Name())
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if len(result.Builders) != 1 {
		t.Fatalf("bad: %#v", result.Builders)
	}
}

func TestParseTemplateFile_stdin(t *testing.T) {
	data := `
	{
		"builders": [{"type": "something"}]
	}
	`

	tf, err := ioutil.TempFile("", "packer")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	defer tf.Close()
	tf.Write([]byte(data))

	// Sync and seek to the beginning so that we can re-read the contents
	tf.Sync()
	tf.Seek(0, 0)

	// Set stdin to something we control
	oldStdin := os.Stdin
	defer func() { os.Stdin = oldStdin }()
	os.Stdin = tf

	result, err := ParseTemplateFile("-")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if len(result.Builders) != 1 {
		t.Fatalf("bad: %#v", result.Builders)
	}
}

func TestParseTemplate_Basic(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [{"type": "something"}]
	}
	`

	result, err := ParseTemplate([]byte(data))
	assert.Nil(err, "should not error")
	assert.NotNil(result, "template should not be nil")
	assert.Length(result.Builders, 1, "one builder")
}

func TestParseTemplate_Invalid(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	// Note there is an extra comma below for a purposeful
	// syntax error in the JSON.
	data := `
	{
		"builders": [],
	}
	`

	result, err := ParseTemplate([]byte(data))
	assert.NotNil(err, "should have an error")
	assert.Nil(result, "should have no result")
}

func TestParseTemplate_InvalidKeys(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	// Note there is an extra comma below for a purposeful
	// syntax error in the JSON.
	data := `
	{
		"builders": [{"type": "foo"}],
		"what is this": ""
	}
	`

	result, err := ParseTemplate([]byte(data))
	assert.NotNil(err, "should have an error")
	assert.Nil(result, "should have no result")
}

func TestParseTemplate_BuilderWithoutType(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [{}]
	}
	`

	_, err := ParseTemplate([]byte(data))
	assert.NotNil(err, "should have error")
}

func TestParseTemplate_BuilderWithNonStringType(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [{
			"type": 42
		}]
	}
	`

	_, err := ParseTemplate([]byte(data))
	assert.NotNil(err, "should have error")
}

func TestParseTemplate_BuilderWithoutName(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [
			{
				"type": "amazon-ebs"
			}
		]
	}
	`

	result, err := ParseTemplate([]byte(data))
	assert.Nil(err, "should not error")
	assert.NotNil(result, "template should not be nil")
	assert.Length(result.Builders, 1, "should have one builder")

	builder, ok := result.Builders["amazon-ebs"]
	assert.True(ok, "should have amazon-ebs builder")
	assert.Equal(builder.Type, "amazon-ebs", "builder should be amazon-ebs")
}

func TestParseTemplate_BuilderWithName(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [
			{
				"name": "bob",
				"type": "amazon-ebs"
			}
		]
	}
	`

	result, err := ParseTemplate([]byte(data))
	assert.Nil(err, "should not error")
	assert.NotNil(result, "template should not be nil")
	assert.Length(result.Builders, 1, "should have one builder")

	builder, ok := result.Builders["bob"]
	assert.True(ok, "should have bob builder")
	assert.Equal(builder.Type, "amazon-ebs", "builder should be amazon-ebs")

	RawConfig := builder.RawConfig
	if RawConfig == nil {
		t.Fatal("missing builder raw config")
	}

	expected := map[string]interface{}{
		"type": "amazon-ebs",
	}

	if !reflect.DeepEqual(RawConfig, expected) {
		t.Fatalf("bad raw: %#v", RawConfig)
	}
}

func TestParseTemplate_BuilderWithConflictingName(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [
			{
				"name": "bob",
				"type": "amazon-ebs"
			},
			{
				"name": "bob",
				"type": "foo",
			}
		]
	}
	`

	_, err := ParseTemplate([]byte(data))
	assert.NotNil(err, "should have error")
}

func TestParseTemplate_Hooks(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{

		"builders": [{"type": "foo"}],

		"hooks": {
			"event": ["foo", "bar"]
		}
	}
	`

	result, err := ParseTemplate([]byte(data))
	assert.Nil(err, "should not error")
	assert.NotNil(result, "template should not be nil")
	assert.Length(result.Hooks, 1, "should have one hook")

	hooks, ok := result.Hooks["event"]
	assert.True(ok, "should have hook")
	assert.Equal(hooks, []string{"foo", "bar"}, "hooks should be correct")
}

func TestParseTemplate_PostProcessors(t *testing.T) {
	data := `
	{
		"builders": [{"type": "foo"}],

		"post-processors": [
			"simple",

			{ "type": "detailed" },

			[ "foo", { "type": "bar" } ]
		]
	}
	`

	tpl, err := ParseTemplate([]byte(data))
	if err != nil {
		t.Fatalf("error parsing: %s", err)
	}

	if len(tpl.PostProcessors) != 3 {
		t.Fatalf("bad number of post-processors: %d", len(tpl.PostProcessors))
	}

	pp := tpl.PostProcessors[0]
	if len(pp) != 1 {
		t.Fatalf("wrong number of configs in simple: %d", len(pp))
	}

	if pp[0].Type != "simple" {
		t.Fatalf("wrong type for simple: %s", pp[0].Type)
	}

	pp = tpl.PostProcessors[1]
	if len(pp) != 1 {
		t.Fatalf("wrong number of configs in detailed: %d", len(pp))
	}

	if pp[0].Type != "detailed" {
		t.Fatalf("wrong type for detailed: %s", pp[0].Type)
	}

	pp = tpl.PostProcessors[2]
	if len(pp) != 2 {
		t.Fatalf("wrong number of configs for sequence: %d", len(pp))
	}

	if pp[0].Type != "foo" {
		t.Fatalf("wrong type for sequence 0: %s", pp[0].Type)
	}

	if pp[1].Type != "bar" {
		t.Fatalf("wrong type for sequence 1: %s", pp[1].Type)
	}
}

func TestParseTemplate_ProvisionerWithoutType(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [{"type": "foo"}],

		"provisioners": [{}]
	}
	`

	_, err := ParseTemplate([]byte(data))
	assert.NotNil(err, "should have error")
}

func TestParseTemplate_ProvisionerWithNonStringType(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [{"type": "foo"}],

		"provisioners": [{
			"type": 42
		}]
	}
	`

	_, err := ParseTemplate([]byte(data))
	assert.NotNil(err, "should have error")
}

func TestParseTemplate_Provisioners(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [{"type": "foo"}],

		"provisioners": [
			{
				"type": "shell"
			}
		]
	}
	`

	result, err := ParseTemplate([]byte(data))
	assert.Nil(err, "should not error")
	assert.NotNil(result, "template should not be nil")
	assert.Length(result.Provisioners, 1, "should have one provisioner")
	assert.Equal(result.Provisioners[0].Type, "shell", "provisioner should be shell")
	assert.NotNil(result.Provisioners[0].RawConfig, "should have raw config")
}

func TestParseTemplate_Variables(t *testing.T) {
	data := `
	{
		"variables": {
			"foo": "bar",
			"bar": null
		},

		"builders": [{"type": "something"}]
	}
	`

	result, err := ParseTemplate([]byte(data))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if result.Variables == nil || len(result.Variables) != 2 {
		t.Fatalf("bad vars: %#v", result.Variables)
	}

	if result.Variables["foo"].Default != "bar" {
		t.Fatal("foo default is not right")
	}

	if result.Variables["foo"].Required {
		t.Fatal("foo should not be required")
	}

	if result.Variables["bar"].Default != "" {
		t.Fatal("default should be empty")
	}

	if !result.Variables["bar"].Required {
		t.Fatal("bar should be required")
	}
}

func TestParseTemplate_variablesBadDefault(t *testing.T) {
	data := `
	{
		"variables": {
			"foo": 7,
		},

		"builders": [{"type": "something"}]
	}
	`

	_, err := ParseTemplate([]byte(data))
	if err == nil {
		t.Fatal("should have error")
	}
}

func TestTemplate_BuildNames(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [
			{
				"name": "bob",
				"type": "amazon-ebs"
			},
			{
				"name": "chris",
				"type": "another"
			}
		]
	}
	`

	result, err := ParseTemplate([]byte(data))
	assert.Nil(err, "should not error")

	buildNames := result.BuildNames()
	sort.Strings(buildNames)
	assert.Equal(buildNames, []string{"bob", "chris"}, "should have proper builds")
}

func TestTemplate_BuildUnknown(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [
			{
				"name": "test1",
				"type": "test-builder"
			}
		]
	}
	`

	template, err := ParseTemplate([]byte(data))
	assert.Nil(err, "should not error")

	build, err := template.Build("nope", nil)
	assert.Nil(build, "build should be nil")
	assert.NotNil(err, "should have error")
}

func TestTemplate_BuildUnknownBuilder(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [
			{
				"name": "test1",
				"type": "test-builder"
			}
		]
	}
	`

	template, err := ParseTemplate([]byte(data))
	assert.Nil(err, "should not error")

	builderFactory := func(string) (Builder, error) { return nil, nil }
	components := &ComponentFinder{Builder: builderFactory}
	build, err := template.Build("test1", components)
	assert.Nil(build, "build should be nil")
	assert.NotNil(err, "should have error")
}

func TestTemplate_Build_NilBuilderFunc(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [
			{
				"name": "test1",
				"type": "test-builder"
			}
		],

		"provisioners": [
			{
				"type": "test-prov"
			}
		]
	}
	`

	template, err := ParseTemplate([]byte(data))
	assert.Nil(err, "should not error")

	defer func() {
		p := recover()
		assert.NotNil(p, "should panic")

		if p != nil {
			assert.Equal(p.(string), "no builder function", "right panic")
		}
	}()

	template.Build("test1", &ComponentFinder{})
}

func TestTemplate_Build_NilProvisionerFunc(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [
			{
				"name": "test1",
				"type": "test-builder"
			}
		],

		"provisioners": [
			{
				"type": "test-prov"
			}
		]
	}
	`

	template, err := ParseTemplate([]byte(data))
	assert.Nil(err, "should not error")

	defer func() {
		p := recover()
		assert.NotNil(p, "should panic")

		if p != nil {
			assert.Equal(p.(string), "no provisioner function", "right panic")
		}
	}()

	template.Build("test1", &ComponentFinder{
		Builder: func(string) (Builder, error) { return nil, nil },
	})
}

func TestTemplate_Build_NilProvisionerFunc_WithNoProvisioners(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [
			{
				"name": "test1",
				"type": "test-builder"
			}
		],

		"provisioners": []
	}
	`

	template, err := ParseTemplate([]byte(data))
	assert.Nil(err, "should not error")

	template.Build("test1", &ComponentFinder{
		Builder: func(string) (Builder, error) { return nil, nil },
	})
}

func TestTemplate_Build(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [
			{
				"name": "test1",
				"type": "test-builder"
			}
		],

		"provisioners": [
			{
				"type": "test-prov"
			}
		],

		"post-processors": [
			"simple",
			[
				"simple",
				{ "type": "simple", "keep_input_artifact": true }
			]
		]
	}
	`

	expectedConfig := map[string]interface{}{
		"type": "test-builder",
	}

	template, err := ParseTemplate([]byte(data))
	assert.Nil(err, "should not error")

	builder := testBuilder()
	builderMap := map[string]Builder{
		"test-builder": builder,
	}

	provisioner := &MockProvisioner{}
	provisionerMap := map[string]Provisioner{
		"test-prov": provisioner,
	}

	pp := new(TestPostProcessor)
	ppMap := map[string]PostProcessor{
		"simple": pp,
	}

	builderFactory := func(n string) (Builder, error) { return builderMap[n], nil }
	ppFactory := func(n string) (PostProcessor, error) { return ppMap[n], nil }
	provFactory := func(n string) (Provisioner, error) { return provisionerMap[n], nil }
	components := &ComponentFinder{
		Builder:       builderFactory,
		PostProcessor: ppFactory,
		Provisioner:   provFactory,
	}

	// Get the build, verifying we can get it without issue, but also
	// that the proper builder was looked up and used for the build.
	build, err := template.Build("test1", components)
	assert.Nil(err, "should not error")

	coreBuild, ok := build.(*coreBuild)
	assert.True(ok, "should be a core build")
	assert.Equal(coreBuild.builder, builder, "should have the same builder")
	assert.Equal(coreBuild.builderConfig, expectedConfig, "should have proper config")
	assert.Equal(len(coreBuild.provisioners), 1, "should have one provisioner")
	assert.Equal(len(coreBuild.postProcessors), 2, "should have pps")
	assert.Equal(len(coreBuild.postProcessors[0]), 1, "should have correct number")
	assert.Equal(len(coreBuild.postProcessors[1]), 2, "should have correct number")
	assert.False(coreBuild.postProcessors[1][0].keepInputArtifact, "shoule be correct")
	assert.True(coreBuild.postProcessors[1][1].keepInputArtifact, "shoule be correct")

	config := coreBuild.postProcessors[1][1].config
	if _, ok := config["keep_input_artifact"]; ok {
		t.Fatal("should not have keep_input_artifact")
	}
}

func TestTemplate_Build_ProvisionerOverride(t *testing.T) {
	assert := asserts.NewTestingAsserts(t, true)

	data := `
	{
		"builders": [
			{
				"name": "test1",
				"type": "test-builder"
			}
		],

		"provisioners": [
			{
				"type": "test-prov",

				"override": {
					"test1": {}
				}
			}
		]
	}
	`

	template, err := ParseTemplate([]byte(data))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	RawConfig := template.Provisioners[0].RawConfig
	if RawConfig == nil {
		t.Fatal("missing provisioner raw config")
	}

	expected := map[string]interface{}{
		"type": "test-prov",
	}

	if !reflect.DeepEqual(RawConfig, expected) {
		t.Fatalf("bad raw: %#v", RawConfig)
	}

	builder := testBuilder()
	builderMap := map[string]Builder{
		"test-builder": builder,
	}

	provisioner := &MockProvisioner{}
	provisionerMap := map[string]Provisioner{
		"test-prov": provisioner,
	}

	builderFactory := func(n string) (Builder, error) { return builderMap[n], nil }
	provFactory := func(n string) (Provisioner, error) { return provisionerMap[n], nil }
	components := &ComponentFinder{
		Builder:     builderFactory,
		Provisioner: provFactory,
	}

	// Get the build, verifying we can get it without issue, but also
	// that the proper builder was looked up and used for the build.
	build, err := template.Build("test1", components)
	assert.Nil(err, "should not error")

	coreBuild, ok := build.(*coreBuild)
	assert.True(ok, "should be a core build")
	assert.Equal(len(coreBuild.provisioners), 1, "should have one provisioner")
	assert.Equal(len(coreBuild.provisioners[0].config), 2, "should have two configs on the provisioner")
}

func TestTemplate_Build_ProvisionerOverrideBad(t *testing.T) {
	data := `
	{
		"builders": [
			{
				"name": "test1",
				"type": "test-builder"
			}
		],

		"provisioners": [
			{
				"type": "test-prov",

				"override": {
					"testNope": {}
				}
			}
		]
	}
	`

	_, err := ParseTemplate([]byte(data))
	if err == nil {
		t.Fatal("should have error")
	}
}

func TestTemplateBuild_variables(t *testing.T) {
	data := `
	{
		"variables": {
			"foo": "bar"
		},

		"builders": [
			{
				"name": "test1",
				"type": "test-builder"
			}
		]
	}
	`

	template, err := ParseTemplate([]byte(data))
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	build, err := template.Build("test1", testComponentFinder())
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	coreBuild, ok := build.(*coreBuild)
	if !ok {
		t.Fatalf("couldn't convert!")
	}

	if len(coreBuild.variables) != 1 {
		t.Fatalf("bad vars: %#v", coreBuild.variables)
	}
}