package config

import (
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/hashicorp/packer/packer-plugin-sdk/template/interpolate"
)

func TestDecode(t *testing.T) {
	type Target struct {
		Name    string
		Address string
		Time    time.Duration
		Trilean Trilean
	}

	cases := map[string]struct {
		Input  []interface{}
		Output *Target
		Opts   *DecodeOpts
	}{
		"basic": {
			[]interface{}{
				map[string]interface{}{
					"name":    "bar",
					"time":    "5s",
					"trilean": "true",
				},
			},
			&Target{
				Name:    "bar",
				Time:    5 * time.Second,
				Trilean: TriTrue,
			},
			nil,
		},

		"empty-string-trilean": {
			[]interface{}{
				map[string]interface{}{
					"trilean": "",
				},
			},
			&Target{
				Trilean: TriUnset,
			},
			nil,
		},

		"variables": {
			[]interface{}{
				map[string]interface{}{
					"name": "{{user `name`}}",
				},
				map[string]interface{}{
					"packer_user_variables": map[string]string{
						"name": "bar",
					},
				},
			},
			&Target{
				Name: "bar",
			},
			nil,
		},

		"filter": {
			[]interface{}{
				map[string]interface{}{
					"name":    "{{user `name`}}",
					"address": "{{user `name`}}",
				},
				map[string]interface{}{
					"packer_user_variables": map[string]string{
						"name": "bar",
					},
				},
			},
			&Target{
				Name:    "bar",
				Address: "{{user `name`}}",
			},
			&DecodeOpts{
				Interpolate: true,
				InterpolateFilter: &interpolate.RenderFilter{
					Include: []string{"name"},
				},
			},
		},

		"build name": {
			[]interface{}{
				map[string]interface{}{
					"name": "{{build_name}}",
				},
				map[string]interface{}{
					"packer_build_name": "foo",
				},
			},
			&Target{
				Name: "foo",
			},
			nil,
		},

		"build type": {
			[]interface{}{
				map[string]interface{}{
					"name": "{{build_type}}",
				},
				map[string]interface{}{
					"packer_builder_type": "foo",
				},
			},
			&Target{
				Name: "foo",
			},
			nil,
		},
	}

	for k, tc := range cases {
		var result Target
		err := Decode(&result, tc.Opts, tc.Input...)
		if err != nil {
			t.Fatalf("err: %s\n\n%s", k, err)
		}

		if !reflect.DeepEqual(&result, tc.Output) {
			t.Fatalf("bad:\n\n%#v\n\n%#v", &result, tc.Output)
		}
	}
}

func TestDecode_fixerRecommendations(t *testing.T) {
	type TestConfig struct {
		Name string
	}

	cases := []struct {
		Reason   string
		Input    []interface{}
		Opts     *DecodeOpts
		Expected string
	}{
		{
			Reason: "If no plugin type is provided, don't try to match fixer options",
			Input: []interface{}{
				map[string]interface{}{
					"name":    "bar",
					"iso_md5": "13123412341234",
				},
			},
			Opts:     &DecodeOpts{},
			Expected: `unknown configuration key: '"iso_md5"'`,
		},
		{
			Reason: "iso_md5 should always recommend packer fix regardless of plugin type",
			Input: []interface{}{
				map[string]interface{}{
					"name":    "bar",
					"iso_md5": "13123412341234",
				},
			},
			Opts:     &DecodeOpts{PluginType: "someplugin"},
			Expected: `Deprecated configuration key: 'iso_md5'`,
		},
		{
			Reason: "filename option should generate a fixer recommendation for the manifest postprocessor",
			Input: []interface{}{
				map[string]interface{}{
					"name":     "bar",
					"filename": "fakefilename",
				},
			},
			Opts:     &DecodeOpts{PluginType: "packer.post-processor.manifest"},
			Expected: `Deprecated configuration key: 'filename'`,
		},
		{
			Reason: "filename option should generate an unknown key error for other plugins",
			Input: []interface{}{
				map[string]interface{}{
					"name":     "bar",
					"filename": "fakefilename",
				},
			},
			Opts:     &DecodeOpts{PluginType: "randomplugin"},
			Expected: `unknown configuration key: '"filename"'`,
		},
	}

	for _, tc := range cases {
		var result TestConfig
		err := Decode(&result, tc.Opts, tc.Input...)
		if err == nil {
			t.Fatalf("Should have had an error: %s", tc.Reason)
		}

		if !strings.Contains(err.Error(), tc.Expected) {
			t.Fatalf("Expected: %s\nActual: %s\n; Reason: %s", tc.Expected, err.Error(), tc.Reason)
		}
	}
}
